diff --git a/Sources/Cloud/CloudNotificationSync.swift b/Sources/Cloud/CloudNotificationSync.swift index ef6d2460bd93..f1dfa36f3d63 100644 --- a/Sources/Cloud/CloudNotificationSync.swift +++ b/Sources/Cloud/CloudNotificationSync.swift @@ -1,3 +1,4 @@ +import Combine import Foundation // Cloud notifications: the VM's cmux-tui daemon is the source of truth. @@ -71,6 +72,28 @@ struct CloudVMNotificationRow: Hashable, Sendable { } } +/// Durable per-machine sync state. `delivered` remembers which retained rows +/// this Mac already turned into local notifications, so a snapshot repair, a +/// reconnect replay, or an app relaunch never re-delivers one. `pendingAcks` +/// are reads the daemon has not confirmed yet; each batch keeps its +/// idempotency key across retries so a retried ack replays instead of +/// committing twice. +struct CloudNotificationSyncState: Codable, Equatable, Sendable { + struct PendingAck: Codable, Equatable, Sendable { + var key: String + var ids: [String] + } + + static let deliveredLimit = 512 + + var delivered: [String] = [] + var pendingAcks: [PendingAck] = [] + + var pendingIDs: Set { + Set(pendingAcks.flatMap(\.ids)) + } +} + /// Pure transitions over `CloudNotificationSyncState`. Every effect the sync /// performs is decided here and only here, so the fault-injection tests cover /// the same code the app runs. @@ -102,16 +125,8 @@ enum CloudNotificationSyncReducer { // locally would lose a read that was recorded before the rows arrived. let delivered = Set(next.delivered) let pending = next.pendingIDs - var read = next.readIDs var deliver: [CloudVMNotificationRow] = [] - for row in rows { - if row.isRead(by: clientID) { - appendReadID(row.id, to: &next, ids: &read) - continue - } - guard !read.contains(row.id), - !delivered.contains(row.id), - !pending.contains(row.id) else { continue } + for row in rows where !row.isRead(by: clientID) && !delivered.contains(row.id) && !pending.contains(row.id) { deliver.append(row) next.delivered.append(row.id) } @@ -136,11 +151,10 @@ enum CloudNotificationSyncReducer { ) -> CloudNotificationSyncState { let byID = Dictionary(rows.map { ($0.id, $0) }, uniquingKeysWith: { first, _ in first }) let pending = state.pendingIDs - let read = state.readIDs var batch: [String] = [] - for id in ids where !batch.contains(id) { + var seen = Set() + for id in ids where seen.insert(id).inserted { if pending.contains(id) { continue } - if read.contains(id) { continue } if let row = byID[id], row.isRead(by: clientID) { continue } batch.append(id) } @@ -152,38 +166,10 @@ enum CloudNotificationSyncReducer { static func ackCompleted(key: String, state: CloudNotificationSyncState) -> CloudNotificationSyncState { var next = state - var acknowledged: [String] = [] - var remaining: [CloudNotificationSyncState.PendingAck] = [] - for batch in next.pendingAcks { - if batch.key == key { - acknowledged.append(contentsOf: batch.ids) - } else { - remaining.append(batch) - } - } - guard !acknowledged.isEmpty else { return state } - next.pendingAcks = remaining - var read = next.readIDs - for id in acknowledged { - appendReadID(id, to: &next, ids: &read) - } + next.pendingAcks.removeAll { $0.key == key } return next } - private static func appendReadID( - _ id: String, - to state: inout CloudNotificationSyncState, - ids: inout Set - ) { - guard ids.insert(id).inserted else { return } - if state.read.count >= CloudNotificationSyncState.deliveredLimit, - let evicted = state.read.first { - state.read.removeFirst() - ids.remove(evicted) - } - state.read.append(id) - } - /// Read-your-write overlay: after the daemon confirmed a batch, the rows /// it named carry this client until the feed delivers the same fact, so /// the unread set cannot flicker back between the ack and its delta. @@ -210,17 +196,66 @@ enum CloudNotificationSyncReducer { state: CloudNotificationSyncState ) -> Set { let pending = state.pendingIDs - let read = state.readIDs var result = Set() - for row in rows where !row.isRead(by: clientID) - && !read.contains(row.id) - && !pending.contains(row.id) { + for row in rows where !row.isRead(by: clientID) && !pending.contains(row.id) { if let terminalID = row.terminalID { result.insert(terminalID) } } return result } } +/// Correlation keys tie a local `TerminalNotification` back to its machine +/// and daemon row, so a local read can be acknowledged and a re-delivery +/// replaces its own prior banner only. +enum CloudNotificationCorrelation { + static let prefix = "cloud-notification:" + + static func key(machineID: String, notificationID: String) -> String { + "\(prefix)\(machineID):\(notificationID)" + } + + static func parse(_ key: String) -> (machineID: String, notificationID: String)? { + guard key.hasPrefix(prefix) else { return nil } + let rest = key.dropFirst(prefix.count) + guard let separator = rest.lastIndex(of: ":") else { return nil } + let machineID = String(rest[.. String { + "cloud.notifications.sync.\(machineID)" + } + + func load(machineID: String) -> CloudNotificationSyncState { + guard let data = defaults.data(forKey: Self.key(machineID: machineID)), + let state = try? JSONDecoder().decode(CloudNotificationSyncState.self, from: data) else { + return CloudNotificationSyncState() + } + return state + } + + func save(_ state: CloudNotificationSyncState, machineID: String) { + guard let data = try? JSONEncoder().encode(state) else { return } + defaults.set(data, forKey: Self.key(machineID: machineID)) + } + + func remove(machineID: String) { + defaults.removeObject(forKey: Self.key(machineID: machineID)) + } +} + /// Where a daemon notification lands locally: the workspace bound to the /// machine, and the pane showing the terminal when one is open here. struct CloudNotificationDeliveryTarget: Equatable, Sendable { @@ -228,20 +263,16 @@ struct CloudNotificationDeliveryTarget: Equatable, Sendable { var panelID: UUID? } -/// One machine's notification sync. Owned by that machine's surface provider, -/// which feeds it every accepted state and reports link reconnects. All -/// effects go through the injected closures so tests drive it without a -/// daemon: `deliver` creates the local notification, `send` performs one -/// `notification.ack` round trip over the link. +/// One machine's notification sync, with injected delivery and acknowledgement effects. @MainActor final class CloudNotificationSync { - /// A declined row stays undelivered for a later fold; a suppressed row is - /// consumed and read in the same fold (see `CloudNotificationDeliveryOutcome`). - typealias Deliverer = @MainActor (CloudVMNotificationRow, CloudNotificationDeliveryTarget) -> CloudNotificationDeliveryOutcome + /// Returns false when the store declined the notification (muted + /// workspace, no store); the row then stays undelivered for a later fold. + typealias Deliverer = @MainActor (CloudVMNotificationRow, CloudNotificationDeliveryTarget) -> Bool typealias TargetResolver = @MainActor (CloudVMNotificationRow) -> CloudNotificationDeliveryTarget? + typealias Handler = @MainActor (CloudVMNotificationRow, CloudNotificationDeliveryTarget) -> Bool typealias AckSender = @MainActor (CloudNotificationSyncState.PendingAck) async throws -> Void typealias UnreadObserver = @MainActor (Set) -> Void - /// Withdraw local banners for rows the machine no longer retains. typealias Withdrawer = @MainActor ([String]) -> Void let machineID: String @@ -249,41 +280,35 @@ final class CloudNotificationSync { private let store: CloudNotificationSyncStore private let deliver: Deliverer private let resolveTarget: TargetResolver + private let handle: Handler? private let send: AckSender private let unreadChanged: UnreadObserver private let withdraw: Withdrawer private let newKey: () -> String - private(set) var state: CloudNotificationSyncState private(set) var rows: [CloudVMNotificationRow] = [] private(set) var unreadTerminalIDs: Set = [] private var flushTask: Task? private var flushRequested = false - /// Set by `retire()`: a replaced sync must not write the shared per-machine - /// key after its provider is gone. private var retired = false - - /// The idempotency key of one `notification.ack` batch. - nonisolated static func mintAckKey() -> String { - "mac-ack-\(UUID().uuidString.lowercased())" - } - init( machineID: String, clientID: String, - store: CloudNotificationSyncStore, - newKey: @escaping () -> String = CloudNotificationSync.mintAckKey, + store: CloudNotificationSyncStore = CloudNotificationSyncStore(), + newKey: @escaping () -> String = { "mac-ack-\(UUID().uuidString.lowercased())" }, resolveTarget: @escaping TargetResolver, deliver: @escaping Deliverer, send: @escaping AckSender, unreadChanged: @escaping UnreadObserver = { _ in }, - withdraw: @escaping Withdrawer = { _ in } + withdraw: @escaping Withdrawer = { _ in }, + handle: Handler? = nil ) { self.machineID = machineID self.clientID = clientID self.store = store self.newKey = newKey self.resolveTarget = resolveTarget + self.handle = handle self.deliver = deliver self.send = send self.unreadChanged = unreadChanged @@ -294,49 +319,40 @@ final class CloudNotificationSync { /// Fold one accepted state. Called after every installed snapshot or /// delta; cheap when the rows did not change. func apply(rows incoming: [CloudVMNotificationRow]) { - guard !retired else { return } rows = incoming let plan = CloudNotificationSyncReducer.plan(rows: incoming, clientID: clientID, state: state) var next = plan.state - var placed: [(CloudVMNotificationRow, CloudNotificationDeliveryTarget)] = [] + var candidates: [(CloudVMNotificationRow, CloudNotificationDeliveryTarget)] = [] for row in plan.deliver { if let target = resolveTarget(row) { - placed.append((row, target)) + candidates.append((row, target)) } else { - // Not consumed: the next fold retries placement. next.delivered.removeAll { $0 == row.id } } } - // Commit before delivering: the store can call back into this sync - // while a banner is recorded (a focused surface reads it at once), and - // that re-entrant commit must build on the state that already counts - // these rows as delivered. commit(next) if !plan.removed.isEmpty { withdraw(plan.removed) } - var undelivered: [String] = [] - var suppressed: [String] = [] - for (row, target) in placed { - switch deliver(row, target) { - case .delivered: - break - case .declined: - undelivered.append(row.id) - case .suppressed: - suppressed.append(row.id) + var placed: [(CloudVMNotificationRow, CloudNotificationDeliveryTarget)] = [] + var handledIDs: [String] = [] + for (row, target) in candidates { + if handle?(row, target) == true { + handledIDs.append(row.id) + } else { + placed.append((row, target)) } } - if !undelivered.isEmpty || !suppressed.isEmpty { - // Built on the current state, not `next`: a delivery can re-enter - // through the store and commit in between. + if !handledIDs.isEmpty { + noteRead(notificationIDs: handledIDs) + } + var undelivered: [String] = [] + for (row, target) in placed where !deliver(row, target) { + undelivered.append(row.id) + } + if !undelivered.isEmpty { var declined = state declined.delivered.removeAll { undelivered.contains($0) } - if !suppressed.isEmpty { - declined = CloudNotificationSyncReducer.recordRead( - ids: suppressed, rows: rows, clientID: clientID, state: declined, newKey: newKey - ) - } commit(declined) } requestFlush() @@ -344,7 +360,6 @@ final class CloudNotificationSync { /// Local reads of this machine's notifications, by daemon row id. func noteRead(notificationIDs: [String]) { - guard !retired else { return } let next = CloudNotificationSyncReducer.recordRead( ids: notificationIDs, rows: rows, @@ -357,43 +372,11 @@ final class CloudNotificationSync { requestFlush() } - /// Local reads by target: every unread row whose current placement the - /// read covers is acknowledged, whether or not it ever became a local - /// record (an admission drop, a row placed elsewhere before the terminal - /// was opened here). Returns the ids so the caller can mirror the read - /// onto local records that live on another workspace. - @discardableResult - func noteRead(coveredBy target: NotificationReadTarget) -> [String] { - guard !retired else { return [] } - let pending = state.pendingIDs - let read = state.readIDs - var ids: [String] = [] - for row in rows where !row.isRead(by: clientID) && !read.contains(row.id) && !pending.contains(row.id) { - if case .all = target { - ids.append(row.id) - } else if let placement = resolveTarget(row), placement.isCovered(by: target) { - ids.append(row.id) - } - } - guard !ids.isEmpty else { return [] } - noteRead(notificationIDs: ids) - return ids - } - /// The link came back. Anything still pending is retried now. func linkDidConnect() { - guard !retired else { return } requestFlush() } - /// Attempts outstanding reads and joins that pass, including persistence. - /// Failed sends remain pending for the next reconnect or accepted state. - func flushPendingReads() async { - requestFlush() - while let flushTask { await flushTask.value } - await store.flush() - } - /// Stop writing on behalf of this machine. A replacement sync for the same /// machine loads the durable state itself; this one must not overwrite /// it from an in-flight flush. @@ -410,10 +393,8 @@ final class CloudNotificationSync { private func commit(_ next: CloudNotificationSyncState) { guard !retired else { return } - if next != state { - state = next - store.save(next, machineID: machineID) - } + state = next + store.save(next, machineID: machineID) let unread = CloudNotificationSyncReducer.unreadTerminalIDs(rows: rows, clientID: clientID, state: next) if unread != unreadTerminalIDs { unreadTerminalIDs = unread @@ -425,7 +406,7 @@ final class CloudNotificationSync { /// the pass and leaves the batch for the next accepted state or reconnect; /// there is no timer and no backoff here because the link owns recovery. private func requestFlush() { - guard !retired, !state.pendingAcks.isEmpty else { return } + guard !state.pendingAcks.isEmpty else { return } if flushTask != nil { flushRequested = true return @@ -446,8 +427,6 @@ final class CloudNotificationSync { while let batch = state.pendingAcks.first { if Task.isCancelled { return } do { - await store.flush() - guard !retired, !Task.isCancelled else { return } try await send(batch) } catch { return @@ -458,3 +437,104 @@ final class CloudNotificationSync { } } } + +extension Notification.Name { + /// Posted on the main actor after a machine's unread terminal set changes. + static let cmuxCloudNotificationUnreadDidChange = Notification.Name("cmux.cloudNotifications.unreadDidChange") +} + +/// App-wide registry of per-machine syncs. Owns the one subscription on the +/// notification store that turns local reads into acknowledgements, and the +/// unread index the Cloud tree renders. +@MainActor +final class CloudNotificationSyncHub { + static let shared = CloudNotificationSyncHub() + + private var syncs: [String: CloudNotificationSync] = [:] + private var notificationGate = CloudMachineNotificationGate() + + /// One admission budget across all live machine providers. Dropped rows remain + /// consumed by the sync so subsequent catalog folds cannot replay a flood. + func admit(_ row: CloudVMNotificationRow, machineID: String) -> Bool { + notificationGate.admit(machineID: machineID, event: CloudMachineNotificationEvent( + id: row.id, terminalID: row.terminalID, title: row.title, body: row.body + )) == .allowed + } + private(set) var unreadTerminalIDs: [String: Set] = [:] + private var storeSubscription: AnyCancellable? + private var unreadCloudKeys: Set? + + func register(_ sync: CloudNotificationSync) { + syncs[sync.machineID] = sync + observeStoreIfNeeded() + } + + func unregister(machineID: String) { + syncs.removeValue(forKey: machineID) + if unreadTerminalIDs.removeValue(forKey: machineID) != nil { + NotificationCenter.default.post(name: .cmuxCloudNotificationUnreadDidChange, object: nil) + } + } + + func sync(machineID: String) -> CloudNotificationSync? { + syncs[machineID] + } + + func setUnread(_ terminalIDs: Set, machineID: String) { + if terminalIDs.isEmpty { + guard unreadTerminalIDs.removeValue(forKey: machineID) != nil else { return } + } else { + guard unreadTerminalIDs[machineID] != terminalIDs else { return } + unreadTerminalIDs[machineID] = terminalIDs + } + #if DEBUG + cmuxDebugLog("cloud.notifications.unread machine=\(machineID) terminals=\(terminalIDs.count)") + #endif + NotificationCenter.default.post(name: .cmuxCloudNotificationUnreadDidChange, object: nil) + } + + /// Correlation keys of cloud notifications that were unread in `previous` + /// and are read or gone in `current`. A dismissal counts as a read: the + /// person chose not to see it again, on this Mac and on the machine. + static func newlyReadKeys(previous: Set, current: [TerminalNotification]) -> (read: Set, unread: Set) { + var unread = Set() + for notification in current where !notification.isRead { + if let key = notification.correlationKey, key.hasPrefix(CloudNotificationCorrelation.prefix) { + unread.insert(key) + } + } + return (previous.subtracting(unread), unread) + } + + private func observeStoreIfNeeded() { + guard storeSubscription == nil, let store = AppDelegate.shared?.notificationStore else { return } + storeSubscription = store.$notifications + .receive(on: RunLoop.main) + .sink { [weak self] notifications in + MainActor.assumeIsolated { + self?.storeDidChange(notifications) + } + } + } + + func storeDidChange(_ notifications: [TerminalNotification]) { + guard let previous = unreadCloudKeys else { + // First observation seeds the baseline. Rows restored from the + // durable feed history as already-read never become acks here; + // the daemon already has them or they were read elsewhere. + unreadCloudKeys = Self.newlyReadKeys(previous: [], current: notifications).unread + return + } + let (read, unread) = Self.newlyReadKeys(previous: previous, current: notifications) + unreadCloudKeys = unread + guard !read.isEmpty else { return } + var byMachine: [String: [String]] = [:] + for key in read { + guard let parsed = CloudNotificationCorrelation.parse(key) else { continue } + byMachine[parsed.machineID, default: []].append(parsed.notificationID) + } + for (machineID, ids) in byMachine { + syncs[machineID]?.noteRead(notificationIDs: ids) + } + } +} diff --git a/Sources/DockSplitStore+TerminalLinkOpening.swift b/Sources/DockSplitStore+TerminalLinkOpening.swift index b9c7126391e1..290d5cb35726 100644 --- a/Sources/DockSplitStore+TerminalLinkOpening.swift +++ b/Sources/DockSplitStore+TerminalLinkOpening.swift @@ -8,24 +8,11 @@ extension DockSplitStore: TerminalLinkOpenContainer { } func terminalLinkWorkingDirectory(for sourcePanelId: UUID) -> String? { - guard let panelId = panelID(forTerminalLinkSourceID: sourcePanelId) else { - return nil - } - return terminalWorkingDirectory(for: panelId) + terminalWorkingDirectory(for: sourcePanelId) } func terminalLinkIsRemoteTerminal(_ sourcePanelId: UUID) -> Bool { - guard let panelId = panelID(forTerminalLinkSourceID: sourcePanelId) else { - return false - } - return detachedSurfaceTransfersByPanelId[panelId]?.isRemoteTerminal == true - } - - func cloudTerminalLinkTarget(url: URL, sourcePanelId: UUID) -> CloudTerminalLinkTarget? { - guard let resource = SurfaceCatalog.shared.resource(forPanel: sourcePanelId), - let address = SurfaceCatalog.shared.machineInfo(for: resource.machine)?.privateAddress, - let target = CmuxTuiSurfaceProvider.cloudTerminalLinkTarget(url: url, resource: resource, privateAddress: address) else { return nil } - return target + detachedSurfaceTransfersByPanelId[sourcePanelId]?.isRemoteTerminal == true } func deferTerminalFileLinkOpen( @@ -39,8 +26,7 @@ extension DockSplitStore: TerminalLinkOpenContainer { } func openTerminalBrowserLink(url: URL, sourcePanelId: UUID, focus: Bool = true) -> Bool { - guard let panelId = panelID(forTerminalLinkSourceID: sourcePanelId), - let sourcePane = paneId(forPanelId: panelId) else { return false } + guard let sourcePane = paneId(forPanelId: sourcePanelId) else { return false } if let targetPane = BrowserRightSidePaneResolver().preferredPane( from: sourcePane, in: bonsplitController @@ -50,12 +36,14 @@ extension DockSplitStore: TerminalLinkOpenContainer { kind: .browser, inPane: targetPane, url: url, - focus: false + focus: focus ) else { return false } - if focus { focusPanelFromDockInteraction( - panelId, - window: NSApp.keyWindow ?? NSApp.mainWindow - ) } + if focus { + focusPanelFromDockInteraction( + panelId, + window: NSApp.keyWindow ?? NSApp.mainWindow + ) + } return true } if focus { noteKeyboardFocusIntent(window: NSApp.keyWindow ?? NSApp.mainWindow) } @@ -63,14 +51,16 @@ extension DockSplitStore: TerminalLinkOpenContainer { kind: .browser, orientation: .horizontal, insertFirst: false, - sourcePanelId: panelId, + sourcePanelId: sourcePanelId, url: url, - focus: false + focus: focus ) else { return false } - if focus { focusPanelFromDockInteraction( - panelId, - window: NSApp.keyWindow ?? NSApp.mainWindow - ) } + if focus { + focusPanelFromDockInteraction( + panelId, + window: NSApp.keyWindow ?? NSApp.mainWindow + ) + } return true } } diff --git a/Sources/Surfaces/CloudBrowserOpenRequest.swift b/Sources/Surfaces/CloudBrowserOpenRequest.swift new file mode 100644 index 000000000000..5cdb90d36b4e --- /dev/null +++ b/Sources/Surfaces/CloudBrowserOpenRequest.swift @@ -0,0 +1,39 @@ +import Foundation + +extension CmuxTuiSurfaceProvider { + /// Private notification title used to request a Mac browser pane from a VM. + static let cloudBrowserOpenNotificationTitle = "cmux.open-url" +} + +extension CmuxTuiSurfaceProvider { + /// Consumes a guest URL request and opens it beside the source terminal. + @MainActor + func handleCloudBrowserOpenRequest( + _ row: CloudVMNotificationRow, + target: CloudNotificationDeliveryTarget + ) -> Bool { + guard row.title == Self.cloudBrowserOpenNotificationTitle else { return false } + guard let terminalID = row.terminalID, + let url = URL(string: row.body), + ["http", "https"].contains(url.scheme?.lowercased() ?? ""), + let sourcePanelID = target.panelID else { + return false + } + let terminalResourceID = SurfaceResourceID(machine: machine, kind: .terminal, key: terminalID) + guard catalog.resources[terminalResourceID] != nil else { + return false + } + + // Match terminal-link policy: the setting chooses an embedded cmux + // browser, while a disabled browser opens the user's system browser. + return TerminalLinkOpenCoordinator( + deferOperation: { operation in operation() } + ).open(TerminalLinkOpenRequest( + rawValue: url.absoluteString, + sourceWorkspaceId: target.workspaceID, + sourcePanelId: sourcePanelID, + workingDirectory: nil, + focus: false + )) + } +} diff --git a/Sources/Surfaces/CmuxTuiSurfaceProviders.swift b/Sources/Surfaces/CmuxTuiSurfaceProviders.swift index aa520b0c17f1..1fdf5c882a71 100644 --- a/Sources/Surfaces/CmuxTuiSurfaceProviders.swift +++ b/Sources/Surfaces/CmuxTuiSurfaceProviders.swift @@ -1,6 +1,4 @@ -import CmuxCore import CmuxFoundation -import CmuxSettings import Foundation /// One cloud machine's resources: its cmux-tui terminals (over the headless link), its /// noVNC screen, and its forwarded ports. Terminals live in the machine's cmux-tui @@ -10,29 +8,23 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { let machineID: String var machine: SurfaceMachineID { .cloud(machineID) } private(set) var info: SurfaceMachineInfo - var summary: VMSummary + private var summary: VMSummary /// This machine's notification sync: VM rows in, local notifications and /// `notification.ack` round trips out. Fed after every accepted state. - var notificationSync: CloudNotificationSync? - var guestURLService: CloudGuestURLService? - var guestURLProjectionVersion: UInt64? - var guestURLTerminalIDs: [String] = [] + private(set) var notificationSync: CloudNotificationSync? /// A local placement (a pane opening, a workspace binding) is a catalog /// change, not a daemon state; it re-runs the fold so rows that had no /// local target get delivered. - var notificationPlacementObserver: NSObjectProtocol? + private var notificationPlacementObserver: NSObjectProtocol? let links: CloudMachineLinkManager unowned let catalog: SurfaceCatalog /// Loopback forwards into this machine's private address over the hub; nil /// when the build has no hub. Owned by the registry, shared by every provider. let portForwards: CloudHubPortForwarder? - let portAccessStore: CloudPortAccessStore - let browserPolicy: @MainActor () -> BrowserURLAllowlistPolicy /// Invalidates suspended work when this provider is stopped or replaced. - var isFeatureSuspended = false - private(set) var lifecycleGeneration: UInt64 = 0 + private var lifecycleGeneration: UInt64 = 0 /// Invalidates an older refresh before it can publish over a newer one. - var refreshGeneration: UInt64 = 0 + private var refreshGeneration: UInt64 = 0 let refreshCoordinator = CloudProviderRefreshCoordinator() /// The only installed daemon graph for this machine. The catalog receives the /// same immutable value with its derived rows in one transaction. @@ -70,21 +62,13 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { /// Native cloud terminals own a manual attachment separate from their /// catalog projection. The provider retains it for the life of the pane. var manualMirrorSessions: [UUID: CloudTuiManualMirrorSession] = [:] - /// Attach loops of reserved panes (restored, or a workspace opened as a whole), by panel id. - var restoredAttachTasks: [UUID: Task] = [:] /// Numeric cmux-tui surface ids are process-local. Re-read the legacy tree /// when the link socket generation changes or an attachment disconnects, /// then reuse the result for the rest of that socket generation. - var manualMirrorSurfaceIDsSocketPath: String? - /// Arms the next attachment pass after one that could not resolve every - /// open pane; reset by a fully resolved pass. - let attachmentRetry = CloudTerminalAttachmentRetryScheduler() - let attachmentLog = CloudTerminalAttachmentLog() - /// Drives the bounded backoff between resolver attempts of one open. - let attachmentClock: any Clock + private var manualMirrorSurfaceIDsSocketPath: String? /// Terminal → tab from the last snapshot, so an exited terminal (whose own selector /// no longer resolves in cmux-tui) can still be closed through its tab. - var tabByTerminal: [String: String] = [:] + private var tabByTerminal: [String: String] = [:] /// Coalesces concurrent first opens of a zero-view terminal. `terminal.project` is a /// mutation, so two local panes racing on the same pool row must share one remote view. // Internal so the manual-mirror extension can share the provider-owned task map. @@ -96,47 +80,40 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { /// the next snapshot can lag it. Keep the exact created row and placement /// until an accepted graph reaches that receipt. This is a transient view /// overlay, never a second remote-state store. - struct PendingRemoteCreation { + private struct PendingRemoteCreation { var resource: SurfaceResource var receipt: CloudVMCursor? let tabID: String? } - var pendingRemoteCreations: [SurfaceResourceID: PendingRemoteCreation] = [:] + private var pendingRemoteCreations: [SurfaceResourceID: PendingRemoteCreation] = [:] /// Rename receipts are transient read-your-write fences. They are keyed by /// daemon identity, not by a local title or projection, because one remote /// tab can be shown in several windows. The canonical graph remains the /// only source of remote values. - enum PendingRemoteRenameKey: Hashable { + private enum PendingRemoteRenameKey: Hashable { case workspace(String) case tab(String) } - struct PendingRemoteRename { + private struct PendingRemoteRename { var name: String var receipt: CloudVMCursor } - var pendingRemoteRenames: [PendingRemoteRenameKey: PendingRemoteRename] = [:] + private var pendingRemoteRenames: [PendingRemoteRenameKey: PendingRemoteRename] = [:] init( summary: VMSummary, links: CloudMachineLinkManager, catalog: SurfaceCatalog, - portForwards: CloudHubPortForwarder? = nil, - attachmentClock: any Clock = ContinuousClock(), - portAccessStore: CloudPortAccessStore? = nil, - browserPolicy: @escaping @MainActor () -> BrowserURLAllowlistPolicy = { BrowserURLAllowlistPolicy() } + portForwards: CloudHubPortForwarder? = nil ) { machineID = summary.id - self.attachmentClock = attachmentClock self.summary = summary self.links = links self.catalog = catalog self.portForwards = portForwards - self.portAccessStore = portAccessStore ?? CloudPortAccessStore() - self.browserPolicy = browserPolicy info = Self.info(from: summary, linkState: summary.status == "running" ? .connecting : .asleep, linkError: nil, stats: nil) installNotificationSync() } var isAwake: Bool { summary.status == "running" } - var providerID: String { summary.provider } /// Port rows are openable only when the machine advertises a preview /// capability or has the private route used by Freestyle. var capabilities: VMCapabilities { summary.capabilities } @@ -144,9 +121,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { capabilities.ports || summary.preferredPrivateAddress != nil } func update(summary: VMSummary) { - guard let current = catalog.provider(for: machine), ObjectIdentifier(current) == ObjectIdentifier(self) else { return } - isFeatureSuspended = false - let previousPrivateAddress = info.privateAddress + guard isRegisteredInCatalog() else { return } refreshGeneration &+= 1 refreshCoordinator.invalidate() self.summary = summary @@ -168,18 +143,8 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { } else { catalog.updateMachine(info, from: self) } - if previousPrivateAddress != info.privateAddress { - refreshCloudBrowserRoutes() - } - } - func stop() async { - suspendForFeatureFlag() - await portAccessStore.remove(machineID: machineID) } - func suspendForFeatureFlag() { - isFeatureSuspended = true - guestURLService?.stop() - guestURLService = nil + func stop() { lifecycleGeneration &+= 1 refreshCoordinator.cancel() for task in browserPaneTasks.values { task.cancel() } @@ -206,13 +171,28 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { for session in manualMirrorSessions.values { session.stop() } manualMirrorSessions.removeAll() manualMirrorSurfaceIDsSocketPath = nil - attachmentRetry.cancel() for task in remoteTerminalProjectionTasks.values { task.cancel() } remoteTerminalProjectionTasks.removeAll() pendingRemoteCreations.removeAll() pendingRemoteRenames.removeAll() acceptedCloudGenerations.removeAll() } + /// Whether this provider is still registered for its machine. Suspended + /// network work must not write through a replacement provider. + func isRegisteredInCatalog() -> Bool { + guard let current = catalog.provider(for: machine) else { return false } + return ObjectIdentifier(current) == ObjectIdentifier(self) + } + func isCurrentLifecycleGeneration(_ generation: UInt64) -> Bool { + lifecycleGeneration == generation + } + /// The generation to capture before detached work that touches panes. + var currentLifecycleGeneration: UInt64 { lifecycleGeneration } + private func isCurrentRefresh(lifecycle: UInt64, refresh: UInt64) -> Bool { + lifecycleGeneration == lifecycle + && refreshGeneration == refresh + && isRegisteredInCatalog() + } /// One refresh pass. Sleeping machines retain their graph without being woken. func performRefresh(force: Bool) async -> Bool { let lifecycle = lifecycleGeneration @@ -309,14 +289,13 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { generation: generation, privateAddress: privateAddress ) - async let snapshotData = link.run(arguments: CloudTuiRequests.snapshotArguments(socketPath: connected.socketPath)) + async let snapshotData = link.run(arguments: CloudTuiCommandLine.snapshotArguments(socketPath: connected.socketPath)) if let refreshedPorts = await refreshedPorts { guard isCurrentRefresh(lifecycle: lifecycle, refresh: generation) else { return false } scannedPorts = refreshedPorts currentPorts = refreshedPorts } watchChanges(link: link, generation: lifecycle) - configureGuestURLOpen(link: link, socketPath: connected.socketPath) let data = try await snapshotData guard isCurrentRefresh(lifecycle: lifecycle, refresh: generation) else { return false } guard let object = try JSONSerialization.jsonObject(with: data) as? [String: Any], @@ -352,9 +331,48 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { // whose items cannot be ordered against the installed snapshot. await link.suspendEventsSubscription() } - guard await reconcileManualMirrorAttachments( - connected: connected, link: link, lifecycle: lifecycle, refresh: generation - ) else { return false } + let needsSurfaceIDRefresh = !manualMirrorSessions.isEmpty + && (manualMirrorSurfaceIDsSocketPath != connected.socketPath + || manualMirrorSessions.values.contains { $0.phase == .disconnected }) + var reconnectableSessionIDs = Set( + manualMirrorSessions.values.map { ObjectIdentifier($0) } + ) + if needsSurfaceIDRefresh { + let sessions = Array(manualMirrorSessions.values) + let resolutions = await resolveManualMirrorSessions( + sessions, + socketPath: connected.socketPath, + link: link + ) + guard isCurrentRefresh(lifecycle: lifecycle, refresh: generation) else { return false } + var allSurfaceIDsResolved = true + var exitedTerminalIDs: Set = [] + for session in sessions { + switch resolutions[session.terminalID] { + case let .resolved(surfaceID): + session.updateRemoteSurfaceID(surfaceID) + reconnectableSessionIDs.insert(ObjectIdentifier(session)) + case .exited: + // The remote shell ended. Stop reconnecting; the pane + // closes when this graph is published. + exitedTerminalIDs.insert(session.terminalID) + session.markSurfaceResolutionUnavailable() + reconnectableSessionIDs.remove(ObjectIdentifier(session)) + case .none, .noPlacement, .unsupported, .failed: + session.markSurfaceResolutionUnavailable() + reconnectableSessionIDs.remove(ObjectIdentifier(session)) + allSurfaceIDsResolved = false + } + } + if allSurfaceIDsResolved { + manualMirrorSurfaceIDsSocketPath = connected.socketPath + } + closePanes(forExitedTerminals: exitedTerminalIDs) + } + for session in manualMirrorSessions.values + where reconnectableSessionIDs.contains(ObjectIdentifier(session)) { + session.reconnect(socketPath: connected.socketPath) + } } catch { guard isCurrentRefresh(lifecycle: lifecycle, refresh: generation) else { return false } let status = await links.status(machineID: machineID) @@ -418,9 +436,8 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { reprojectRestoredPanes(generation: lifecycle) return snapshotEstablishedCurrentGraph } - @discardableResult - func installSnapshotIfNewer(_ incoming: CloudVMState, requestVersion: UInt64? = nil) -> Bool { + private func installSnapshotIfNewer(_ incoming: CloudVMState, requestVersion: UInt64? = nil) -> Bool { guard acceptsIncomingGeneration(incoming.cursor) else { #if DEBUG cmuxDebugLog("cloud.state.snapshotIgnored machine=\(machineID) reason=old-generation") @@ -437,11 +454,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { #endif return false } - guard requestVersion == nil || requestVersion == cloudStateInstallVersion || incoming == current else { - return false - } cloudState = incoming - cloudStateInstallVersion &+= 1 retirePendingRemoteRenames(observed: incoming) return true } @@ -556,15 +569,15 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { /// Publishes the authoritative graph and every derived row in one catalog /// transaction. Display and forwarded-port rows are machine capabilities, so /// they join the daemon graph here without becoming a second session state. - func publish( + private func publish( _ state: CloudVMState, ports: [Int], reconcileTitles: Bool = true, observation: CloudVMStateObservation = .current ) { - guard cloudState == state, canPublishCloudState(state) else { return } var pool: [SurfaceResource] = [] // The control plane's resolved kind is authoritative. Freestyle snapshot + // ids are opaque and cannot tell us whether the machine has a desktop. if summary.resolvedKind.hasDesktop { pool.append(desktopDisplayResource()) } @@ -578,35 +591,42 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { tabByTerminal = CmuxTuiSnapshotParser.tabByTerminal(fromSnapshot: snapshot) } info.remoteWorkspaces = remoteWorkspaces(for: state) - let acceptedObservation = observationWithPendingWrites(observation) catalog.replaceCloudState( state, resources: resources, info: info, - observation: acceptedObservation + observation: observationWithPendingWrites(observation) ) if reconcileTitles { - catalog.reconcileCloudRemoteState(machine: machine, state: state, observation: acceptedObservation) + catalog.reconcileCloudRemoteState(machine: machine, state: state) } closePanesForVanishedRemoteTerminals(observation: observation) } - func publishDelta( + /// Applies a contiguous event to the catalog's canonical graph. Row-local changes rebuild + /// only their affected terminal, browser, or display rows. A topology change crosses a + /// relationship boundary and uses the authoritative complete publication path. + private func publishDelta( _ state: CloudVMState, impact: CloudVMStateDeltaImpact, ports: [Int], reconcileTitles: Bool ) { - guard cloudState == state, canPublishCloudState(state) else { return } if impact.requiresFullResourceRebuild { publish(state, ports: ports, reconcileTitles: reconcileTitles) return } var affected = impact.resourceIDs + // A full publish can erase an optimistic create while its receipt is + // still ahead of the accepted graph. Include those identities in a + // delta patch as well, so every publication path applies the same + // read-your-write overlay atomically. affected.formUnion(pendingRemoteCreations.keys) var resources = CmuxTuiSnapshotParser.resources(from: state, matching: affected) resources = resourcesWithPendingCreations(resources, state: state) + // A desktop is a machine capability even when no workspace currently points at it. + // Include that pool row only when the delta actually touched a display identity. if summary.resolvedKind.hasDesktop, affected.contains(SurfaceResourceID(machine: machine, kind: .display, key: "display:1")) { resources = CmuxTuiSnapshotParser.mergingDisplays( @@ -616,24 +636,18 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { } info.remoteWorkspaces = remoteWorkspaces(for: state) let previousIDs = Set(catalog.snapshot.resources(on: machine).map(\.id)) - let acceptedObservation = observationWithPendingWrites() let changed = catalog.applyCloudStateResourcePatch( state, resources: resources, affectedResourceIDs: affected, info: info, - observation: acceptedObservation + observation: observationWithPendingWrites() ) if reconcileTitles { - catalog.cloudWorkspaceRenameService.reconcileRemoteState( - machine: machine, - state: state, - catalog: catalog, - observation: acceptedObservation, - affectedResources: affected, - workspaceNamesChanged: false - ) + catalog.reconcileCloudRemoteState(machine: machine, state: state) } + // A newly restored terminal may need its attach pane materialized. Existing rows do not + // need a full projection scan for every title event. if changed.contains(where: { $0.kind == .terminal && !previousIDs.contains($0) }) { reprojectRestoredPanes(generation: lifecycleGeneration) } @@ -643,7 +657,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { /// Closes the panes of terminals the resolver reported as exited. The /// graph-driven sweep covers the usual case; this covers a daemon that /// still lists an exited terminal because a stale tab row survives it. - func closePanes(forExitedTerminals terminalIDs: Set) { + private func closePanes(forExitedTerminals terminalIDs: Set) { guard !terminalIDs.isEmpty else { return } for (panelID, session) in manualMirrorSessions where terminalIDs.contains(session.terminalID) { closeManualMirrorPane(panelID: panelID, terminalID: session.terminalID) @@ -651,7 +665,6 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { } private func closeManualMirrorPane(panelID: UUID, terminalID: String) { - restoredAttachTasks.removeValue(forKey: panelID)?.cancel() materializedPanels.remove(panelID) manualMirrorSessions.removeValue(forKey: panelID)?.stop() guard let workspace = AppDelegate.shared?.workspace(containingSurfaceID: panelID) else { return } @@ -692,12 +705,185 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { } } - static func remoteWorkspaces(_ state: CloudVMState) -> [SurfaceRemoteWorkspace] { + private static func remoteWorkspaces(_ state: CloudVMState) -> [SurfaceRemoteWorkspace] { state.workspaces.map { SurfaceRemoteWorkspace(id: $0.id, name: $0.name, index: $0.index, focused: $0.focused) } } + /// Merges pending mutation receipts into derived rows until an accepted + /// graph reaches each receipt. The canonical graph is never edited here. + /// A generation change, or a cursorless snapshot after a versioned receipt, + /// retires the overlay because the old placement cannot be proven to exist. + private func resourcesWithPendingCreations( + _ resources: [SurfaceResource], + state: CloudVMState? + ) -> [SurfaceResource] { + var merged = resources + var completed: [SurfaceResourceID] = [] + for (resourceID, pending) in pendingRemoteCreations where resourceID.machine == machine { + if let state { + if let receipt = pending.receipt { + guard let cursor = state.cursor, + cursor.generation == receipt.generation else { + completed.append(resourceID) + continue + } + if cursor.revision >= receipt.revision { + // At or beyond the commit, the accepted graph is the + // source of truth, including an intentional close. + completed.append(resourceID) + continue + } + } else if pendingCreationIsVisible(pending, in: state) { + // Legacy mutation responses have no ordering fence. Stop + // overlaying as soon as the exact path is observed. + completed.append(resourceID) + continue + } + } + mergePendingCreation(pending, into: &merged) + } + for resourceID in completed { + pendingRemoteCreations.removeValue(forKey: resourceID) + } + return merged + } + + private func pendingCreationIsVisible( + _ pending: PendingRemoteCreation, + in state: CloudVMState + ) -> Bool { + guard state.lookupIndex.terminal(id: pending.resource.id.key) != nil else { return false } + guard let tabID = pending.tabID else { return true } + return state.lookupIndex.tab(id: tabID) != nil + } + + private func mergePendingCreation( + _ pending: PendingRemoteCreation, + into resources: inout [SurfaceResource] + ) { + guard let pendingView = pending.resource.remoteViews?.first else { + if !resources.contains(where: { $0.id == pending.resource.id }) { + resources.append(pending.resource) + } + return + } + guard let index = resources.firstIndex(where: { $0.id == pending.resource.id }) else { + resources.append(pending.resource) + return + } + var resource = resources[index] + var views = resource.remoteViews ?? [] + if !views.contains(where: { $0.tabID == pendingView.tabID }) { + views.append(pendingView) + resource.remoteViews = views + if resource.remoteWorkspace == nil { + resource.remoteWorkspace = pendingView.workspace + } + } + resources[index] = resource + } + + private func remoteWorkspaces(for state: CloudVMState?) -> [SurfaceRemoteWorkspace]? { + var result = state.map(Self.remoteWorkspaces) ?? info.remoteWorkspaces ?? [] + var seen = Set(result.map(\.id)) + for pending in pendingRemoteCreations.values { + guard let workspace = pending.resource.remoteWorkspace, + seen.insert(workspace.id).inserted else { continue } + result.append(workspace) + } + return result.isEmpty ? nil : result + } + + private func pendingMutationMetadata() -> [CloudVMPendingMutation] { + var writes = pendingRemoteCreations.map { resourceID, pending in + CloudVMPendingMutation( + kind: .terminalCreate, + resource: resourceID, + remoteWorkspaceID: pending.resource.remoteWorkspace?.id, + remoteTabID: pending.tabID, + name: pending.resource.remoteViews?.first?.name, + receipt: pending.receipt + ) + } + writes.append(contentsOf: pendingRemoteRenames.map { key, pending in + switch key { + case .workspace(let id): + return CloudVMPendingMutation( + kind: .workspaceRename, + resource: nil, + remoteWorkspaceID: id, + remoteTabID: nil, + name: pending.name, + receipt: pending.receipt + ) + case .tab(let id): + return CloudVMPendingMutation( + kind: .tabRename, + resource: nil, + remoteWorkspaceID: nil, + remoteTabID: id, + name: pending.name, + receipt: pending.receipt + ) + } + }) + return writes.sorted { left, right in + if left.kind.rawValue != right.kind.rawValue { + return left.kind.rawValue < right.kind.rawValue + } + let leftID = left.resource?.rawValue ?? left.remoteWorkspaceID ?? left.remoteTabID ?? "" + let rightID = right.resource?.rawValue ?? right.remoteWorkspaceID ?? right.remoteTabID ?? "" + return leftID < rightID + } + } + + private func observationWithPendingWrites( + _ base: CloudVMStateObservation = .current + ) -> CloudVMStateObservation { + var observation = base + let pending = pendingMutationMetadata() + observation.pendingWrites = pending.isEmpty ? nil : pending + return observation + } + + private func publishPendingMutationMetadata() { + catalog.updateCloudPendingWrites( + on: machine, + writes: pendingMutationMetadata(), + from: self + ) + } + + private func pendingCreation(for resourceID: SurfaceResourceID) -> PendingRemoteCreation? { + pendingRemoteCreations[resourceID] + } + + private func pendingCreation(forTabID tabID: String) -> PendingRemoteCreation? { + pendingRemoteCreations.values.first { $0.tabID == tabID } + } + + /// Advances a pending receipt after a follow-up rename commits before the + /// creation snapshot arrives. This keeps the optimistic row and its tab + /// label coherent without inventing a second canonical graph. + private func recordPendingRename(tabID: String, name: String, revision: UInt64) { + for resourceID in Array(pendingRemoteCreations.keys) { + guard var pending = pendingRemoteCreations[resourceID], pending.tabID == tabID else { continue } + if let receipt = pending.receipt { + guard revision >= receipt.revision else { continue } + pending.receipt = CloudVMCursor(generation: receipt.generation, revision: revision) + } + if var views = pending.resource.remoteViews, + let viewIndex = views.firstIndex(where: { $0.tabID == tabID }) { + views[viewIndex].name = name + pending.resource.remoteViews = views + } + pendingRemoteCreations[resourceID] = pending + } + publishPendingMutationMetadata() + } + private func recordPendingRemoteRename( workspaceID: String, name: String, @@ -710,7 +896,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { publishPendingMutationMetadata() } - func recordPendingRemoteRename( + private func recordPendingRemoteRename( tabID: String, name: String, receipt: CloudVMCursor @@ -722,7 +908,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { publishPendingMutationMetadata() } - func pendingRemoteRename(for key: PendingRemoteRenameKey) -> PendingRemoteRename? { + private func pendingRemoteRename(for key: PendingRemoteRenameKey) -> PendingRemoteRename? { pendingRemoteRenames[key] } @@ -738,6 +924,121 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { ) } + /// Runs one close-family command, reconnecting and retrying ONCE when the attempt + /// died with the link ("cmux-tui link exited with status …": a dropped tunnel kills + /// the whole client run). Safe here because every close verb is idempotent — a + /// second attempt against an already-closed target is `selector.not_found`, which + /// the callers already tolerate. Non-idempotent verbs (create, run) must not use it. + private func runCloseCommand(_ arguments: (_ socketPath: String) -> [String]) async throws -> Data { + let connected = try await links.connected(machineID: machineID) + guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } + do { + return try await link.run(arguments: arguments(connected.socketPath)) + } catch { + // selector.not_found is a real answer, not a transport failure. + if Self.isSelectorNotFound(error) { throw error } + let reconnected = try await links.connected(machineID: machineID) + guard let fresh = await links.link(machineID: machineID) else { throw error } + return try await fresh.run(arguments: arguments(reconnected.socketPath)) + } + } + + // MARK: Headless terminal I/O (agent primitives; no pane involved) + + /// Type `text` into the remote terminal exactly as given (no newline appended). + func sendText(terminalID: String, text: String) async throws { + let connected = try await links.connected(machineID: machineID) + guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } + _ = try await link.run(arguments: CloudTuiCommandLine.writeArguments(socketPath: connected.socketPath, terminalID: terminalID, text: text)) + } + + /// Press named keys (`enter`, `ctrl+c`, …) in the remote terminal, in order. + func sendKeys(terminalID: String, keys: [String]) async throws { + let connected = try await links.connected(machineID: machineID) + guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } + _ = try await link.run(arguments: CloudTuiCommandLine.keysArguments(socketPath: connected.socketPath, terminalID: terminalID, keys: keys)) + } + + /// The remote terminal's visible screen, as the daemon reports it + /// (`cols`, `rows`, `cursor_row`, `cursor_col`, `cursor_visible`, `text`). + func readScreen(terminalID: String) async throws -> [String: Any] { + let connected = try await links.connected(machineID: machineID) + guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } + let data = try await link.run(arguments: CloudTuiCommandLine.screenReadArguments(socketPath: connected.socketPath, terminalID: terminalID)) + return (try JSONSerialization.jsonObject(with: data) as? [String: Any]) ?? [:] + } + + /// Block until the screen matches `pattern` (or the daemon-side timeout elapses): + /// `{matched, text}`. The link call itself is given headroom beyond the timeout. + func waitForScreen(terminalID: String, pattern: String, timeoutMs: Int?) async throws -> [String: Any] { + let connected = try await links.connected(machineID: machineID) + guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } + // Non-positive requests mean the daemon default, so the link headroom is computed + // from the same value the daemon will use; huge requests are clamped so the + // Duration math cannot overflow. + let effectiveMs = Self.clampedWaitTimeoutMs(timeoutMs) + let linkTimeout = Duration.milliseconds(effectiveMs + 5_000) + let data = try await link.run( + arguments: CloudTuiCommandLine.screenWaitArguments(socketPath: connected.socketPath, terminalID: terminalID, pattern: pattern, timeoutMs: effectiveMs), + timeout: linkTimeout + ) + return (try JSONSerialization.jsonObject(with: data) as? [String: Any]) ?? [:] + } + + /// `screen wait` default when the caller gives no (or a non-positive) timeout. + nonisolated static let defaultWaitTimeoutMs = 30_000 + /// Upper bound for one `screen wait` (an hour): long enough for any build, short + /// enough that the link call and the socket call stay finite. + nonisolated static let maxWaitTimeoutMs = 3_600_000 + + /// Pure, so the nonisolated socket handler can normalize before hopping actors. + nonisolated static func clampedWaitTimeoutMs(_ requested: Int?) -> Int { + guard let requested, requested > 0 else { return defaultWaitTimeoutMs } + return min(requested, maxWaitTimeoutMs) + } + + /// `terminal close`; a terminal whose process already exited is gone from + /// cmux-tui's selectors, so its tab is closed instead. Either way the resource + /// leaves the catalog now and the next snapshot confirms. + func closeTerminal(_ id: SurfaceResourceID) async throws { + try await closeTerminal(id, fallbackTabID: nil) + } + + func closeTerminal(_ id: SurfaceResourceID, fallbackTabID: String?) async throws { + pendingRemoteCreations.removeValue(forKey: id) + do { + _ = try await runCloseCommand { CloudTuiCommandLine.closeTerminalArguments(socketPath: $0, terminalID: id.key) } + } catch { + guard let tabID = fallbackTabID ?? tabByTerminal[id.key], Self.isSelectorNotFound(error) else { throw error } + _ = try await runCloseCommand { CloudTuiCommandLine.closeTabArguments(socketPath: $0, tabID: tabID) } + } + closeLocalPanes(showing: [id]) + catalog.remove(id, from: self) + scheduleRefresh() + } + + /// A closed terminal has no pane to show any more: every local pane that projected it + /// goes too, instead of lingering as a dead attach the person has to close by hand. + private func closeLocalPanes(showing ids: [SurfaceResourceID]) { + let wanted = Set(ids) + for projection in catalog.snapshot.projections where wanted.contains(projection.resource) { + SurfacePaneFactory.close(panelID: projection.panelID, in: projection.workspaceID) + } + } + + /// `workspace close`: its tabs go with it, its terminals detach into the pool + /// (`spec/cli.md`: only `terminal close` kills) — the protocol contract, and what + /// the sidebar's "Close Workspace (Keep Terminals)" promises. Callers wanting the + /// full delete (`vm.workspace_delete`, the sidebar's "Delete Workspace and + /// Terminals…") go through `CloudTreeNodeActions.deleteWorkspaceAndTerminals`, + /// which closes each terminal first. + func closeRemoteWorkspace(id: String) async throws { + _ = try await runCloseCommand { CloudTuiCommandLine.closeWorkspaceArguments(socketPath: $0, workspaceID: id) } + info.remoteWorkspaces = info.remoteWorkspaces?.filter { $0.id != id } + catalog.updateMachine(info, from: self) + scheduleRefresh() + } + /// cmux-tui's `selector.not_found` error body, surfaced by `link.run` as the /// command's output text. static func isSelectorNotFound(_ error: Error) -> Bool { @@ -762,18 +1063,6 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { remoteView: SurfaceRemoteView?, at destination: SurfaceDestination, focus: Bool - ) async throws -> SurfaceProjection { - try await materialize(resource, remoteView: remoteView, at: destination, focus: focus, adopting: nil) - } - - /// `adopting` binds the attachment to an optimistic pane the workspace already - /// inserted instead of creating a second one; browsers never reserve panes. - func materialize( - _ resource: SurfaceResource, - remoteView: SurfaceRemoteView?, - at destination: SurfaceDestination, - focus: Bool, - adopting reservation: CloudTerminalPaneReservation? ) async throws -> SurfaceProjection { let created: (workspaceID: UUID, panelID: UUID) var createdPlacement: SurfaceRemotePlacement? @@ -781,14 +1070,15 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { case .terminal: let manual = try await materializeManualMirrorTerminal( resource, - remoteTabID: (remoteView ?? Self.defaultRemoteView(for: resource))?.tabID, at: destination, - focus: focus, - adopting: reservation + focus: focus ) created = (manual.workspaceID, manual.panelID) createdPlacement = manual.remotePlacement case .display, .browser: + // Ports and the desktop are reached through the user-space + // WireGuard hub on a loopback forward: no system VPN, no + // extension approval, on every build (`CloudPortRoutePlan`). created = try await materializeBrowserPane(resource, at: destination, focus: focus) } materializedPanels.insert(created.panelID) @@ -805,7 +1095,37 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { ) } - func recordCreatedTerminal( + /// A new terminal in the machine's cmux-tui session (`workspace run -- argv`). + func createTerminal(command: [String]?, cwd: String?, name: String?, remoteWorkspaceID: String?) async throws -> SurfaceResource { + try await createTerminal(command: command, cwd: cwd, name: name, remoteWorkspaceID: remoteWorkspaceID, onExit: nil) + } + + /// The same, choosing the daemon's exit policy: `"keep"` retains the tab and final + /// screen after the process exits (a sender that reads the process's last lines as + /// its result needs that); nil is the daemon default, `close`. + func createTerminal(command: [String]?, cwd: String?, name: String?, remoteWorkspaceID: String?, onExit: String?) async throws -> SurfaceResource { + let connected = try await links.connected(machineID: machineID) + guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } + // Resolve the active workspace inside the daemon mutation. A stale Mac + // catalog must never bootstrap a second workspace during concurrent creates. + let requestedWorkspace = remoteWorkspaceID?.trimmingCharacters(in: .whitespacesAndNewlines) + let workspaceID = requestedWorkspace.flatMap { $0.isEmpty ? nil : $0 } ?? "current" + let argv = CloudTuiCommandLine.commandStartingIn( + cwd: cwd, + command: (command?.isEmpty == false ? command : nil) ?? CloudTuiCommandLine.defaultTerminalCommand + ) + let data = try await link.run(arguments: CloudTuiCommandLine.runArguments(socketPath: connected.socketPath, workspaceID: workspaceID, command: argv, onExit: onExit)) + guard let object = try JSONSerialization.jsonObject(with: data) as? [String: Any], + let created = CmuxTuiSnapshotParser.createdTerminal(fromRunResult: object) else { + throw ProviderError.terminalNotCreated(String(data: data, encoding: .utf8) ?? "") + } + guard let resolvedWorkspaceID = created.workspaceID ?? (workspaceID == "current" ? nil : workspaceID) else { + throw ProviderError.noWorkspaceOnMachine(machineID) + } + return recordCreatedTerminal(created, workspaceID: resolvedWorkspaceID, name: name, cwd: cwd) + } + + private func recordCreatedTerminal( _ created: CmuxTuiSnapshotParser.CreatedTerminalPath, workspaceID: String, name: String?, @@ -831,7 +1151,6 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { port: nil, url: nil ) - resource.creationAttachment = created.attachment if let tabID = created.tabID { resource.remoteViews = [SurfaceRemoteView( tabID: tabID, @@ -868,7 +1187,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } for attempt in 0..<8 { try Task.checkCancellation() - let data = try await link.run(arguments: CloudTuiRequests.snapshotArguments(socketPath: connected.socketPath)) + let data = try await link.run(arguments: CloudTuiCommandLine.snapshotArguments(socketPath: connected.socketPath)) guard let snapshot = try JSONSerialization.jsonObject(with: data) as? [String: Any], let state = CmuxTuiSnapshotParser.state(fromSnapshot: snapshot, machine: machine), let revision = state.cursor?.revision else { throw ProviderError.invalidSnapshot(machineID) } @@ -892,8 +1211,8 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { let connected = try await links.connected(machineID: machineID) guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } let workspaceName = name?.trimmingCharacters(in: .whitespacesAndNewlines) - var arguments = CloudTuiRequests.createWorkspaceArguments(socketPath: connected.socketPath, name: workspaceName) - if let expectedRevision { arguments = arguments.adding(["expected_revision": String(expectedRevision)]) } + var arguments = CloudTuiCommandLine.createWorkspaceArguments(socketPath: connected.socketPath, name: workspaceName) + if let expectedRevision { arguments += ["--expected-revision", String(expectedRevision)] } let created = try await link.run(arguments: arguments) guard let object = try JSONSerialization.jsonObject(with: created) as? [String: Any], let id = CmuxTuiSnapshotParser.createdWorkspace(fromResult: object) else { @@ -976,6 +1295,93 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { _ = await refreshCurrentGraph(force: true) } + /// Rename one placement-local daemon tab. This is the canonical path used by a + /// cloud-tree workspace row and by a local pane that remembers its remote tab id. + func renameRemoteTab(id: String, name: String) async throws { + try Task.checkCancellation() + let normalizedName = CloudRemoteRenameName(rawValue: name).wireValue + + // Creation and rename can arrive back-to-back. Refresh before validating the + // target, but use the creation receipt when the accepted snapshot still + // trails it. The receipt's revision is a CAS fence, not a timing guess. + let refreshEstablishedCurrentGraph = await refreshCurrentGraph(force: true) + try Task.checkCancellation() + let pendingCreation = pendingCreation(forTabID: id) + let pendingRename = pendingRemoteRename(for: .tab(id)) + let observed = cloudState + let previous = observed?.tabs.first(where: { $0.id == id }) + let observedCursor = observed?.cursor + let pendingReceipt = [pendingCreation?.receipt, pendingRename?.receipt] + .compactMap { $0 } + .filter { receipt in + guard let observedCursor else { return true } + return receipt.generation == observedCursor.generation + } + .max { $0.revision < $1.revision } + let authority = CloudVMRemoteMutationAuthority.resolve( + refreshEstablishedCurrentGraph: refreshEstablishedCurrentGraph, + hasAcceptedState: observed != nil, + targetVisible: previous != nil, + hasVersionedCursor: observedCursor != nil, + hasPendingReceipt: pendingReceipt != nil + ) + switch authority { + case .currentGraph: + guard let previous, let observedCursor else { + throw ProviderError.stateUnavailable(machineID) + } + do { + let receipt = try await sendRenameTab( + id: id, + name: normalizedName, + expectedRevision: observedCursor.revision + ) + let validated = try validatedReceipt(receipt, against: observedCursor) + recordPendingRemoteRename(tabID: id, name: normalizedName, receipt: validated) + recordPendingRename(tabID: id, name: normalizedName, revision: validated.revision) + } catch { + guard Self.isRevisionConflict(error), + await refreshCurrentGraph(force: true), + let latest = cloudState, + let current = latest.tabs.first(where: { $0.id == id }), + let latestCursor = latest.cursor, + (current.name ?? "") == (previous.name ?? "") else { throw error } + let receipt = try await sendRenameTab( + id: id, + name: normalizedName, + expectedRevision: latestCursor.revision + ) + let validated = try validatedReceipt(receipt, against: latestCursor) + recordPendingRemoteRename(tabID: id, name: normalizedName, receipt: validated) + recordPendingRename(tabID: id, name: normalizedName, revision: validated.revision) + } + case .pendingReceipt: + guard let receipt = pendingReceipt else { + throw ProviderError.stateUnavailable(machineID) + } + let committed = try await sendRenameTab( + id: id, + name: normalizedName, + expectedRevision: receipt.revision + ) + let validated = try validatedReceipt(committed, against: receipt) + recordPendingRemoteRename(tabID: id, name: normalizedName, receipt: validated) + recordPendingRename(tabID: id, name: normalizedName, revision: validated.revision) + case .snapshotOnly: + throw ProviderError.snapshotOnly(machineID) + case .unavailable: + throw ProviderError.stateUnavailable(machineID) + case .targetMissing: + throw SurfaceCatalogError.unsupported( + String(localized: "cloudTree.error.renameTerminalNoView", defaultValue: "This terminal is not open in a remote workspace.") + ) + } + // The daemon event normally installs this before the command exits. The + // explicit read is the barrier for older clients that do not stream deltas. + try Task.checkCancellation() + _ = await refreshCurrentGraph(force: true) + } + /// Compatibility operation for callers that intentionally mean “all views”. /// It is kept separate from `renameRemoteTab` so an ambiguous terminal identity /// can never silently rename an arbitrary placement. @@ -1037,10 +1443,10 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { ) } - // A same-text user rename must still claim an automatic name. - let pendingTargets = targets.filter { - $0.previousName != normalizedName || observed?.lookupIndex.tab(id: $0.tabID)?.nameAuthority?.source == .auto - } + // Do not spend a revision on a view that already has the requested name. + // This also reduces the window in which another client can race the + // compatibility fan-out. + let pendingTargets = targets.filter { $0.previousName != normalizedName } if pendingTargets.isEmpty { return } var lastCommitCursor = observedCursor @@ -1124,7 +1530,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { name: String, expectedRevision: UInt64? ) async throws -> CloudVMCursor? { - let data = try await link.run(arguments: CloudTuiRequests.renameWorkspaceArguments( + let data = try await link.run(arguments: CloudTuiCommandLine.renameWorkspaceArguments( socketPath: socketPath, workspaceID: workspaceID, name: name, @@ -1136,10 +1542,10 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { return CmuxTuiSnapshotParser.mutationCursor(fromResult: object) } - func sendRenameTab(id: String, name: String, expectedRevision: UInt64? = nil) async throws -> CloudVMCursor { + private func sendRenameTab(id: String, name: String, expectedRevision: UInt64? = nil) async throws -> CloudVMCursor { let connected = try await links.connected(machineID: machineID) guard let link = await links.link(machineID: machineID) else { throw ProviderError.machineAsleep(machineID) } - let data = try await link.run(arguments: CloudTuiRequests.renameTabArguments( + let data = try await link.run(arguments: CloudTuiCommandLine.renameTabArguments( socketPath: connected.socketPath, tabID: id, name: name, @@ -1154,7 +1560,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { return receipt } - func validatedReceipt( + private func validatedReceipt( _ receipt: CloudVMCursor?, against expected: CloudVMCursor ) throws -> CloudVMCursor { @@ -1203,7 +1609,6 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { /// The terminal lives in the machine's session; only the local pane went away. func projectionDidEnd(_ projection: SurfaceProjection) { browserPaneTasks.removeValue(forKey: projection.panelID)?.cancel() - restoredAttachTasks.removeValue(forKey: projection.panelID)?.cancel() materializedPanels.remove(projection.panelID) manualMirrorSessions.removeValue(forKey: projection.panelID)?.stop() } @@ -1211,7 +1616,6 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { @discardableResult func discardMaterialization(_ projection: SurfaceProjection) -> Bool { browserPaneTasks.removeValue(forKey: projection.panelID)?.cancel() - restoredAttachTasks.removeValue(forKey: projection.panelID)?.cancel() materializedPanels.remove(projection.panelID) manualMirrorSessions.removeValue(forKey: projection.panelID)?.stop() SurfacePaneFactory.close(panelID: projection.panelID, in: projection.workspaceID) @@ -1252,6 +1656,14 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { } } + /// Compatibility fallback for callers that only have a terminal identity. A terminal with + /// several views has no safe implicit placement. Returning nil keeps the projection + /// placement-neutral until a caller supplies an exact tab id. + static func defaultRemoteView(for resource: SurfaceResource) -> SurfaceRemoteView? { + guard resource.kind != .display, let views = resource.remoteViews, views.count == 1 else { return nil } + return views[0] + } + private func attachCommand(terminalID: String) async throws -> String { let connected = try await links.connected(machineID: machineID) guard let clientURL = CloudTuiClientPaths.clientURL() else { @@ -1260,6 +1672,9 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { return CloudTuiCommandLine.attachShellCommand(clientPath: clientURL.path, socketPath: connected.socketPath, terminalID: terminalID) } + /// The tokened wrapper URL the control plane mints for a port; the desktop adds the + /// noVNC query the `cmux vm desktop` recipe uses. + /// What the connecting/failure screen calls the pane: " · Desktop" or ":". static func paneLabel(machineID: String, port: Int, desktop: Bool) -> String { desktop ? "\(machineID) · \(String(localized: "cloudTree.node.desktop", defaultValue: "Desktop"))" @@ -1288,17 +1703,13 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { /// Turn a VM-local browser URL into the same URL on the VM private address. /// Path, query, fragment, scheme, and port stay unchanged. nonisolated static func privateBrowserURL(_ raw: String, privateAddress: String) -> String? { - guard let parts = URLComponents(string: raw), - RemoteLoopbackProxyAlias.isLoopbackHost(parts.host ?? "") else { return nil } - return CloudPortRoutePlan.privateURL(raw, address: privateAddress)?.absoluteString - } - - /// Shared Cloud terminal-link conversion for Workspace and Dock containers. - nonisolated static func cloudTerminalLinkTarget(url: URL, resource: SurfaceResource, privateAddress: String) -> CloudTerminalLinkTarget? { - guard resource.kind == .terminal, resource.machine.cloudMachineID != nil, - let rewritten = privateBrowserURL(url.absoluteString, privateAddress: privateAddress), - let privateURL = URL(string: rewritten) else { return nil } - return CloudTerminalLinkTarget(url: privateURL) + guard var parts = URLComponents(string: raw), + let host = parts.host?.lowercased(), + host == "localhost" || host == "127.0.0.1" || host == "::1" else { + return nil + } + parts.host = privateAddress + return parts.url?.absoluteString } /// Add the local URL used when this resource is projected on the Mac. @@ -1335,7 +1746,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { if !force, let cached = portsCache, Date.now.timeIntervalSince(cached.at) < portsTTL { return cached.ports } - guard let arguments = CloudTuiRequests.listeningPortsArguments(socketPath: socketPath), + guard let arguments = CloudTuiCommandLine.listeningPortsArguments(socketPath: socketPath), let data = try? await link.run(arguments: arguments), let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let stdout = object["stdout"] as? String else { @@ -1348,6 +1759,131 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { return ports } + // MARK: Notifications + private func installNotificationSync() { + // The registry never creates a provider while the managed-device + // policy disables Cloud, so no policy check is repeated here. + let machineID = self.machineID + let clientID = CloudTuiClientPaths().notificationClientID() + let sync = CloudNotificationSync( + machineID: machineID, + clientID: clientID, + resolveTarget: { [weak self] row in self?.notificationDeliveryTarget(for: row) }, + deliver: { [weak self] row, target in self?.deliverNotification(row, to: target) ?? false }, + send: { [weak self] batch in + guard let self else { throw ProviderError.machineAsleep(machineID) } + let connected = try await self.links.connected(machineID: machineID) + guard let link = await self.links.link(machineID: machineID) else { + throw ProviderError.machineAsleep(machineID) + } + _ = try await link.run(arguments: CloudTuiCommandLine.notificationAckArguments( + socketPath: connected.socketPath, + clientID: clientID, + notificationIDs: batch.ids, + idempotencyKey: batch.key + )) + }, + unreadChanged: { terminalIDs in + CloudNotificationSyncHub.shared.setUnread(terminalIDs, machineID: machineID) + }, + withdraw: { ids in + // `cmux notify --clear` on the machine, or ledger eviction: + // the local banners for those rows go with them. + guard let store = AppDelegate.shared?.notificationStore else { return } + let keys = Set(ids.map { CloudNotificationCorrelation.key(machineID: machineID, notificationID: $0) }) + for notification in store.notifications where notification.correlationKey.map(keys.contains) == true { + store.remove(id: notification.id) + } + }, + handle: { [weak self] row, target in self?.handleCloudBrowserOpenRequest(row, target: target) ?? false } + ) + notificationSync = sync + CloudNotificationSyncHub.shared.register(sync) + notificationPlacementObserver = NotificationCenter.default.addObserver( + forName: SurfaceCatalog.didChangeNotification, + object: nil, + queue: .main + ) { [weak self] _ in + MainActor.assumeIsolated { + guard let self, let state = self.cloudState else { return } + self.syncNotifications(from: state) + } + } + } + + private func syncNotifications(from state: CloudVMState) { + guard let notificationSync else { return } + let rows = CloudVMNotificationRow.rows(from: state) + notificationSync.apply(rows: rows) + #if DEBUG + cmuxDebugLog("cloud.notifications.sync machine=\(machineID) revision=\((state.cursor?.revision).map(String.init) ?? "nil") rows=\(rows.count) unreadTerminals=\(notificationSync.unreadTerminalIDs.count) pending=\(notificationSync.state.pendingAcks.count)") + #endif + } + + /// The pane showing the terminal when one is open on this Mac, else the + /// local workspace bound to the terminal's remote workspace, else any + /// local workspace bound to the machine. No local placement means the row + /// stays undelivered until one exists; the Cloud tree still shows the dot. + private func notificationDeliveryTarget(for row: CloudVMNotificationRow) -> CloudNotificationDeliveryTarget? { + if let terminalID = row.terminalID { + let resourceID = SurfaceResourceID(machine: machine, kind: .terminal, key: terminalID) + let projections = catalog.projections(of: resourceID) + if projections.count == 1, let projection = projections.first { + return CloudNotificationDeliveryTarget(workspaceID: projection.workspaceID, panelID: projection.panelID) + } + } + if row.title == Self.cloudBrowserOpenNotificationTitle { return nil } + let remoteWorkspaceID = row.terminalID.flatMap { terminalID -> String? in + guard let state = cloudState else { return nil } + for tab in state.tabs where tab.contentID == terminalID { + guard let pane = state.lookupIndex.pane(id: tab.paneID), + let screen = state.lookupIndex.screen(id: pane.screenID) else { continue } + return screen.workspaceID + } + return nil + } + let bound = (AppDelegate.shared?.tabManager?.tabs ?? []).filter { $0.cloudVMBinding?.vmID == machineID } + if let remoteWorkspaceID, + let exact = bound.first(where: { $0.cloudVMBinding?.remoteWorkspaceID == remoteWorkspaceID }) { + return CloudNotificationDeliveryTarget(workspaceID: exact.id, panelID: nil) + } + if let any = bound.first { + return CloudNotificationDeliveryTarget(workspaceID: any.id, panelID: nil) + } + return nil + } + + private func deliverNotification(_ row: CloudVMNotificationRow, to target: CloudNotificationDeliveryTarget) -> Bool { + guard row.title != Self.cloudBrowserOpenNotificationTitle else { return false } + guard let store = AppDelegate.shared?.notificationStore else { return false } + guard CloudNotificationSyncHub.shared.admit(row, machineID: machineID) else { return true } + let terminalTitle = row.terminalID.flatMap { cloudState?.lookupIndex.terminal(id: $0)?.title } ?? "" + let machineName = summary.preferredName + let subtitle: String + if let explicit = row.subtitle { + // The producer's own subtitle wins, as `cmux notify --subtitle` does locally. + subtitle = explicit + } else if terminalTitle.isEmpty { + subtitle = machineName + } else { + subtitle = String( + format: String(localized: "cloudNotification.subtitle.machine", defaultValue: "%@ on %@"), + terminalTitle, + machineName + ) + } + return store.addNotification( + tabId: target.workspaceID, + surfaceId: target.panelID, + title: row.title, + subtitle: subtitle, + body: row.body, + retargetsToLiveSurfaceOwner: target.panelID != nil, + correlationKey: CloudNotificationCorrelation.key(machineID: machineID, notificationID: row.id), + origin: .cloudVM(machineID: machineID) + ) != nil + } + private func watchChanges(link: CloudMachineLink, generation: UInt64) { guard generation == lifecycleGeneration else { return } if let watchedLink, watchedLink === link, changeWatcher != nil { return } @@ -1367,27 +1903,24 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { } } } + private func changeWatcherDidEnd(_ watcherID: UUID) { guard changeWatcherID == watcherID else { return } changeWatcher = nil watchedLink = nil changeWatcherID = nil - catalog.markCloudStateStale(on: machine, reason: "event_feed_ended") scheduleRefresh() } + private func handle(_ change: CloudMachineLink.Change, from link: CloudMachineLink) async { // Events from a retired link can arrive after a reconnect. They are // never allowed to mutate the graph owned by the replacement link. guard watchedLink === link else { return } switch change { case .connected: - guestURLService?.stop() - // A link can recover after the shared WireGuard hub briefly fails - // to publish its listener. Always refresh on the successful - // connection edge so stale hub/link errors disappear from the - // machine, ports, and display rows immediately. - scheduleRefresh() + if cloudState == nil { scheduleRefresh() } notificationSync?.linkDidConnect() + case .snapshot(let cursor, _, let payload): guard let object = try? JSONSerialization.jsonObject(with: payload) as? [String: Any], let incoming = CmuxTuiSnapshotParser.state(fromSnapshot: object, machine: machine), @@ -1398,12 +1931,10 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { if installSnapshotIfNewer(incoming) { clearStateRecovery() await link.setEventsCursor(incoming.cursor) - guard watchedLink === link, canPublishCloudState(incoming) else { return } var subscriptionResumed = false if let cursor = incoming.cursor { subscriptionResumed = await link.resumeEventsSubscription(from: cursor) } - guard watchedLink === link, canPublishCloudState(incoming) else { return } if CloudVMEventFeedRecoveryDecision.shouldClearWarning( snapshotCursor: incoming.cursor, subscriptionResumed: subscriptionResumed @@ -1416,6 +1947,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { reprojectRestoredPanes(generation: lifecycleGeneration) syncNotifications(from: incoming) } + case .delta(let cursor, let previousRevision, let revision, let payload): switch CloudVMStateSyncDecision.forDelta( generation: cursor.generation, @@ -1471,7 +2003,6 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { eventsFeedWarning = nil clearStateRecovery() await link.setEventsCursor(next.cursor) - guard watchedLink === link, canPublishCloudState(next) else { return } info.linkState = .connected info.linkError = nil let titlesChanged = current.workspaces != next.workspaces || current.tabs != next.tabs @@ -1483,6 +2014,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { ) syncNotifications(from: next) } + case .streamEnded(let reason, _): // A stream gap, unknown item, or transport end is a full-state // barrier. The snapshot command is the only safe recovery source; @@ -1492,6 +2024,7 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { eventsFeedWarning = reason } scheduleStateRecoveryRefresh() + case .unknown: // An unknown item is a synchronization barrier, but it is not proof that the // transport is dead. Coalesce the expensive snapshot repair and stop after a small @@ -1499,10 +2032,12 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { scheduleStateRecoveryRefresh() } } - /// Coalesces event-feed barriers behind bounded snapshot recovery. Until it succeeds, - /// retained graph data is diagnostic history, not a current directory report. + + /// Coalesces malformed, unknown, and relationship-invalid events behind one bounded + /// snapshot refresh. A daemon can emit many bad lines during a protocol mismatch; one + /// pending task and a finite budget protect both the machine and the UI from a refresh + /// storm while preserving a visible warning after recovery is exhausted. private func scheduleStateRecoveryRefresh() { - catalog.markCloudStateStale(on: machine, reason: "event_feed_recovery") guard stateRecoveryCount < Self.stateRecoveryLimit else { eventsFeedWarning = "state_recovery_exhausted" stateRecoveryRefreshQueued = false @@ -1525,15 +2060,16 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { } } } + private func clearStateRecovery() { stateRecoveryRefreshTask?.cancel() stateRecoveryRefreshTask = nil stateRecoveryRefreshQueued = false stateRecoveryCount = 0 } + /// Mutations also request a snapshot as a safety check. One main-actor yield /// coalesces calls made in the same transaction without adding a time guess. - func reconcileRemovedRemoteWorkspace(_ id: String) { info.remoteWorkspaces = info.remoteWorkspaces?.filter { $0.id != id }; catalog.updateMachine(info, from: self) } func scheduleRefresh() { let lifecycle = lifecycleGeneration guard scheduledRefresh == nil else { return } @@ -1545,35 +2081,32 @@ final class CmuxTuiSurfaceProvider: SurfaceProvider { await self.refreshCurrentGraph(force: false) } } + + /// A restored session brings back the pane (with its UUID) but not the attach process: + /// the catalog resolved the record into a projection whose panel is a placeholder shell. + /// Replace it in place with a real attach pane, as a tab of the same pane, then close + /// the placeholder. private func reprojectRestoredPanes(generation: UInt64) { guard isCurrentLifecycleGeneration(generation), isRegisteredInCatalog() else { return } reprojectRestoredBrowserPanes(generation: generation) let terminals = catalog.snapshot.resources(on: machine).filter { $0.kind == .terminal } for terminal in terminals { for projection in catalog.projections(of: terminal.id) where !materializedPanels.contains(projection.panelID) { - guard cloudState.map({ catalog.cloudWorkspaceProjectionCoordinator.retainsProjection(projection, in: $0) }) != false, - let workspace = AppDelegate.shared?.workspace(containingSurfaceID: projection.panelID), + guard AppDelegate.shared?.workspace(containingSurfaceID: projection.panelID) != nil, let paneID = SurfacePaneFactory.paneID(ofPanel: projection.panelID, in: projection.workspaceID) else { continue } - if let reservation = workspace.cloudPendingCreations[projection.panelID] { - attachReservedTerminalPane(reservation, resource: terminal, remoteTabID: projection.remoteTabID) - continue - } - // Claimed before any async hop so a burst of refreshes cannot re-project twice. + // Claimed before the async hop so a burst of refreshes cannot re-project twice. materializedPanels.insert(projection.panelID) - guard let reservation = workspace.reserveCloudTerminalPane( - machine: machine, - at: .tab(workspaceID: projection.workspaceID, paneID: paneID, index: nil), - focus: false - ) else { - materializedPanels.remove(projection.panelID) - continue + Task { @MainActor [weak self] in + guard let self, self.isCurrentLifecycleGeneration(generation) else { return } + await self.reprojectManualMirror( + resource: terminal, + projection: projection, + paneID: paneID, + generation: generation + ) } - catalog.replaceProjection(projection, withPanel: reservation.panelID, in: projection.workspaceID, remotePlacement: nil) - workspace.clearCloudMaterializationFailure(surfaceID: projection.panelID) - SurfacePaneFactory.close(panelID: projection.panelID, in: projection.workspaceID) - attachReservedTerminalPane(reservation, resource: terminal, remoteTabID: projection.remoteTabID) } } } diff --git a/Sources/TerminalHTMLFileBrowserAction.swift b/Sources/TerminalHTMLFileBrowserAction.swift index 65479f22b2bd..a205f1896519 100644 --- a/Sources/TerminalHTMLFileBrowserAction.swift +++ b/Sources/TerminalHTMLFileBrowserAction.swift @@ -28,7 +28,8 @@ struct TerminalHTMLFileBrowserAction { guard let browserURL = browserURL(for: fileURL) else { return false } return container.openTerminalBrowserLink( url: browserURL, - sourcePanelId: sourcePanelId + sourcePanelId: sourcePanelId, + focus: true ) } } diff --git a/Sources/TerminalLinkOpenContainer.swift b/Sources/TerminalLinkOpenContainer.swift index 0b227cb1d5d5..714657f63535 100644 --- a/Sources/TerminalLinkOpenContainer.swift +++ b/Sources/TerminalLinkOpenContainer.swift @@ -9,8 +9,6 @@ protocol TerminalLinkOpenContainer: AnyObject { func terminalLinkWorkingDirectory(for sourcePanelId: UUID) -> String? func terminalLinkIsRemoteTerminal(_ sourcePanelId: UUID) -> Bool - func cloudTerminalLinkTarget(url: URL, sourcePanelId: UUID) -> CloudTerminalLinkTarget? - @discardableResult func deferTerminalFileLinkOpen( sourcePanelId: UUID, @@ -21,13 +19,3 @@ protocol TerminalLinkOpenContainer: AnyObject { @discardableResult func openTerminalBrowserLink(url: URL, sourcePanelId: UUID, focus: Bool) -> Bool } - -struct CloudTerminalLinkTarget: Sendable, Equatable { - let url: URL -} - -extension TerminalLinkOpenContainer { - func openTerminalBrowserLink(url: URL, sourcePanelId: UUID) -> Bool { - openTerminalBrowserLink(url: url, sourcePanelId: sourcePanelId, focus: true) - } -} diff --git a/Sources/TerminalLinkOpenCoordinator.swift b/Sources/TerminalLinkOpenCoordinator.swift index 7936e278cd2e..e27901262d74 100644 --- a/Sources/TerminalLinkOpenCoordinator.swift +++ b/Sources/TerminalLinkOpenCoordinator.swift @@ -18,25 +18,18 @@ struct TerminalLinkOpenCoordinator { private let containerResolver: @MainActor (UUID?, UUID?) -> (any TerminalLinkOpenContainer)? private let externalOpen: @MainActor @Sendable (URL) -> Bool private let fileOpen: any FileOpening - private let recordsDiagnostics: Bool private let deferOperation: @MainActor (@escaping @MainActor @Sendable () -> Void) -> Void - /// Creates a coordinator using the supplied routing collaborators. - /// - /// The production preferred-editor service is created at the point of - /// opening so it always reads the current editor setting. init( defaults: UserDefaults = .standard, containerResolver: (@MainActor (UUID?, UUID?) -> (any TerminalLinkOpenContainer)?)? = nil, externalOpen: @escaping @MainActor @Sendable (URL) -> Bool = { NSWorkspace.shared.open($0) }, fileOpen: (any FileOpening)? = nil, - recordsDiagnostics: Bool = true, deferOperation: @escaping @MainActor (@escaping @MainActor @Sendable () -> Void) -> Void = { operation in Task { @MainActor in operation() } } ) { self.defaults = defaults - self.recordsDiagnostics = recordsDiagnostics self.externalNavigationHandler = BrowserExternalNavigationHandler( defaults: defaults, openURL: externalOpen @@ -47,7 +40,6 @@ struct TerminalLinkOpenCoordinator { self.deferOperation = deferOperation } - /// Opens a terminal link according to the source terminal and URL policy. @discardableResult func open(_ request: TerminalLinkOpenRequest) -> Bool { log("link.openURL raw=\(request.rawValue)") @@ -55,7 +47,6 @@ struct TerminalLinkOpenCoordinator { let trimmed = request.rawValue.trimmingCharacters(in: .whitespacesAndNewlines) let container = containerResolver(request.sourceWorkspaceId, request.sourcePanelId) var normalizedOpenURLString = request.rawValue - let isExplicitLocalFileURL = isExplicitFileURL(trimmed) let canResolveLocalFilePath: Bool if let sourcePanelId = request.sourcePanelId, let container { @@ -65,30 +56,16 @@ struct TerminalLinkOpenCoordinator { } if !trimmed.isEmpty, canResolveLocalFilePath, - let reference = TerminalPathResolver().resolveOpenURLFileReference( + let resolvedPath = TerminalPathResolver().resolveOpenURLFilePath( trimmed, cwd: resolvedWorkingDirectory(request: request, container: container) ) { - if let line = reference.line, !isExplicitLocalFileURL { - log( - "link.openURL resolvedAsFileLocation=\(reference.path):\(line)" + - (reference.column.map { ":\($0)" } ?? "") - ) - PreferredEditorService(defaults: defaults).open( - URL(fileURLWithPath: reference.path), - line: reference.line, - column: reference.column - ) - return true - } - - if !isExplicitLocalFileURL, - CommandClickFileOpenRouter.shouldRouteInCmux( - path: reference.path, - defaults: defaults - ) { - let fileURL = URL(fileURLWithPath: reference.path) - log("link.openURL resolvedAsFilePath=\(reference.path)") + let fileURL = URL(fileURLWithPath: resolvedPath) + if CommandClickFileOpenRouter.shouldRouteInCmux( + path: resolvedPath, + defaults: defaults + ) { + log("link.openURL resolvedAsFilePath=\(resolvedPath)") return routeLocalFile( fileURL, request: request, @@ -96,9 +73,7 @@ struct TerminalLinkOpenCoordinator { unavailableReason: "file route unavailable" ) } - if !isExplicitLocalFileURL { - normalizedOpenURLString = reference.path - } + normalizedOpenURLString = resolvedPath } guard let target = resolveTerminalOpenURLTarget(normalizedOpenURLString) else { @@ -130,18 +105,15 @@ struct TerminalLinkOpenCoordinator { ) } - let cloudURL = request.sourcePanelId.flatMap { - container?.cloudTerminalLinkTarget(url: target.url, sourcePanelId: $0)?.url - } - let destinationURL = cloudURL ?? target.url guard BrowserLinkOpenSettings.openTerminalLinksInCmuxBrowser(defaults: defaults) else { - return openExternally(destinationURL, reason: "cmux browser disabled") + return openExternally(target.url, reason: "cmux browser disabled") } + switch target { - case .external: - return openExternally(destinationURL, reason: "external target") - case .embeddedBrowser: - return openEmbeddedBrowserURL(destinationURL, request: request, container: container) + case .external(let url): + return openExternally(url, reason: "external target") + case .embeddedBrowser(let url): + return openEmbeddedBrowserURL(url, request: request, container: container) } } @@ -271,16 +243,26 @@ struct TerminalLinkOpenCoordinator { "container=\(container.terminalLinkContainerDebugName) surfaceId=\(sourcePanelId)" ) - if !request.focus { - return container.openTerminalBrowserLink(url: url, sourcePanelId: sourcePanelId, focus: false) - } deferOperation { [self] in - let currentContainer = self.containerResolver(request.sourceWorkspaceId, sourcePanelId) + let currentContainer = self.containerResolver( + request.sourceWorkspaceId, + sourcePanelId + ) let openedInBrowser = BrowserAvailabilitySettings.isEnabled(defaults: self.defaults) - && currentContainer?.openTerminalBrowserLink(url: url, sourcePanelId: sourcePanelId) == true + && currentContainer?.openTerminalBrowserLink( + url: url, + sourcePanelId: sourcePanelId, + focus: request.focus + ) == true if openedInBrowser { return } - self.log("link.openURL embedded open failed, opening externally host=\(host) surfaceId=\(sourcePanelId) url=\(url)") - if !self.externalOpen(url) { NSSound.beep() } + + self.log( + "link.openURL embedded open failed, opening externally " + + "host=\(host) surfaceId=\(sourcePanelId) url=\(url)" + ) + if !self.externalOpen(url) { + NSSound.beep() + } } return true } @@ -308,11 +290,6 @@ struct TerminalLinkOpenCoordinator { return externalOpen(url) } - /// Returns whether a raw link is an explicit local `file` URL. - private func isExplicitFileURL(_ rawValue: String) -> Bool { - URL(string: rawValue)?.scheme?.caseInsensitiveCompare("file") == .orderedSame - } - private static func resolveContainer( sourceWorkspaceId: UUID?, sourcePanelId: UUID? @@ -330,7 +307,7 @@ struct TerminalLinkOpenCoordinator { private func log(_ message: @autoclosure () -> String) { #if DEBUG - if recordsDiagnostics { cmuxDebugLog(message()) } + cmuxDebugLog(message()) #endif } } diff --git a/Sources/TerminalLinkOpenRequest.swift b/Sources/TerminalLinkOpenRequest.swift index b029aedb080d..c3c802aac987 100644 --- a/Sources/TerminalLinkOpenRequest.swift +++ b/Sources/TerminalLinkOpenRequest.swift @@ -6,5 +6,20 @@ struct TerminalLinkOpenRequest: Sendable { let sourceWorkspaceId: UUID? let sourcePanelId: UUID? let workingDirectory: String? - var focus: Bool = true + /// Whether an embedded browser pane should receive focus after opening. + let focus: Bool + + init( + rawValue: String, + sourceWorkspaceId: UUID?, + sourcePanelId: UUID?, + workingDirectory: String?, + focus: Bool = true + ) { + self.rawValue = rawValue + self.sourceWorkspaceId = sourceWorkspaceId + self.sourcePanelId = sourcePanelId + self.workingDirectory = workingDirectory + self.focus = focus + } } diff --git a/cmux.xcodeproj/project.pbxproj b/cmux.xcodeproj/project.pbxproj index 426ba041cd90..b7b000d0e2c0 100644 --- a/cmux.xcodeproj/project.pbxproj +++ b/cmux.xcodeproj/project.pbxproj @@ -709,6 +709,7 @@ C62A16251EC09E72A897A2A3 /* CloudAgentSkillLauncher+CodingAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D50DCB9771484FE2B966B07 /* CloudAgentSkillLauncher+CodingAgent.swift */; }; EAEF1B227CA5A458005D2EAD /* CloudAgentSkillLauncher+LauncherError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0110525EA7A8A51561759317 /* CloudAgentSkillLauncher+LauncherError.swift */; }; 68B60071A7049D7CD9C2CFD7 /* CloudAgentSkillLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7BA0E69E17F61ECF69C9025 /* CloudAgentSkillLauncher.swift */; }; + C12346000000000000000002 /* CloudBrowserOpenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12346000000000000000001 /* CloudBrowserOpenRequest.swift */; }; C6D4C5A38097BB05AB98E460 /* CloudAgentSkillLauncherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 763D217526D3832668F47568 /* CloudAgentSkillLauncherTests.swift */; }; C12537000000000000000315 /* CloudAttachmentReconcileDecision.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12537000000000000000316 /* CloudAttachmentReconcileDecision.swift */; }; C12537000000000000000317 /* CloudAttachmentReconcileDecisionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12537000000000000000318 /* CloudAttachmentReconcileDecisionTests.swift */; }; @@ -4687,6 +4688,7 @@ 4D50DCB9771484FE2B966B07 /* CloudAgentSkillLauncher+CodingAgent.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "CloudAgentSkillLauncher+CodingAgent.swift"; sourceTree = ""; }; 0110525EA7A8A51561759317 /* CloudAgentSkillLauncher+LauncherError.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "CloudAgentSkillLauncher+LauncherError.swift"; sourceTree = ""; }; A7BA0E69E17F61ECF69C9025 /* CloudAgentSkillLauncher.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CloudAgentSkillLauncher.swift; sourceTree = ""; }; + C12346000000000000000001 /* CloudBrowserOpenRequest.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CloudBrowserOpenRequest.swift; sourceTree = ""; }; 763D217526D3832668F47568 /* CloudAgentSkillLauncherTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CloudAgentSkillLauncherTests.swift; sourceTree = ""; }; C12537000000000000000316 /* CloudAttachmentReconcileDecision.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudAttachmentReconcileDecision.swift; sourceTree = ""; }; C12537000000000000000318 /* CloudAttachmentReconcileDecisionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudAttachmentReconcileDecisionTests.swift; sourceTree = ""; }; @@ -8273,6 +8275,7 @@ 3B78E9F9B9C233418960CB37 /* SurfacePortEndpointCache.swift */, A12093000000000000000001 /* CmuxTuiSurfaceProvider+Error.swift */, 7A0CE1000000000000000727 /* CmuxTuiSurfaceProvider+PortForward.swift */, + C12346000000000000000001 /* CloudBrowserOpenRequest.swift */, EF71108A70863A2942D129D2 /* CmuxTuiSurfaceProvider+Notifications.swift */, B51835ECCEF581663B12E497 /* CmuxTuiSurfaceProvider+GuestURL.swift */, ); @@ -12839,6 +12842,7 @@ C62A16251EC09E72A897A2A3 /* CloudAgentSkillLauncher+CodingAgent.swift in Sources */, EAEF1B227CA5A458005D2EAD /* CloudAgentSkillLauncher+LauncherError.swift in Sources */, 68B60071A7049D7CD9C2CFD7 /* CloudAgentSkillLauncher.swift in Sources */, + C12346000000000000000002 /* CloudBrowserOpenRequest.swift in Sources */, C12537000000000000000315 /* CloudAttachmentReconcileDecision.swift in Sources */, D0F2A0000000000000000005 /* CloudBannerDismissal.swift in Sources */, D12539060000000000000001 /* CloudBannerDismissalSignatures.swift in Sources */, diff --git a/cmuxTests/CloudNotificationSyncTests.swift b/cmuxTests/CloudNotificationSyncTests.swift index 5397207c726d..54e262be4c42 100644 --- a/cmuxTests/CloudNotificationSyncTests.swift +++ b/cmuxTests/CloudNotificationSyncTests.swift @@ -161,6 +161,38 @@ struct CloudNotificationSyncTests { #expect(none == next.state) } + @Test @MainActor func handledControlRowsAreConsumedAndAcknowledgedWithoutDelivery() async { + let defaults = UserDefaults(suiteName: "cmux.tests.cloud-notification-handler.\(UUID().uuidString)")! + let store = CloudNotificationSyncStore(defaults: defaults) + defer { defaults.removePersistentDomain(forName: defaults.volatileDomainNames.first ?? "") } + let control = Self.row("open", terminal: "term_00000000000000000000000000000002") + var handled: [String] = [] + var delivered: [String] = [] + var acked: [[String]] = [] + let sendCompleted = AsyncStream.makeStream() + let sync = CloudNotificationSync( + machineID: "vm-handler", + clientID: Self.me, + store: store, + newKey: { "handler-key" }, + resolveTarget: { _ in CloudNotificationDeliveryTarget(workspaceID: UUID(), panelID: UUID()) }, + deliver: { row, _ in delivered.append(row.id); return true }, + send: { batch in + acked.append(batch.ids) + sendCompleted.continuation.yield(()) + }, + handle: { row, _ in handled.append(row.id); return row.id == control.id } + ) + + sync.apply(rows: [control]) + #expect(await sendCompleted.stream.first(where: { _ in true }) != nil) + + #expect(handled == [control.id]) + #expect(delivered.isEmpty) + #expect(acked == [[control.id]]) + #expect(sync.unreadTerminalIDs.isEmpty) + } + @Test func evictionPrunesDeliveredButNeverAPendingAck() { let a = Self.row("a") let b = Self.row("b") diff --git a/cmuxTests/TerminalLinkOpenCoordinatorTests.swift b/cmuxTests/TerminalLinkOpenCoordinatorTests.swift index ad27f4aafd1b..775dc833b183 100644 --- a/cmuxTests/TerminalLinkOpenCoordinatorTests.swift +++ b/cmuxTests/TerminalLinkOpenCoordinatorTests.swift @@ -109,6 +109,17 @@ struct TerminalLinkOpenCoordinatorTests { secondURL.absoluteString, ]) #expect(externallyOpened.isEmpty) + + let focusedBeforeHostOpen = store.focusedPanelId + let hostURL = try #require(URL(string: "https://example.com/host-request")) + #expect(coordinator.open(TerminalLinkOpenRequest( + rawValue: hostURL.absoluteString, + sourceWorkspaceId: nil, + sourcePanelId: terminalPanelId, + workingDirectory: nil, + focus: false + ))) + #expect(store.focusedPanelId == focusedBeforeHostOpen) } @Test( diff --git a/docs/cloud-agent-primitives.md b/docs/cloud-agent-primitives.md index 447f430c5d2a..5e80f938ce69 100644 --- a/docs/cloud-agent-primitives.md +++ b/docs/cloud-agent-primitives.md @@ -16,6 +16,7 @@ keep running when the laptop closes. | `vm push` / `vm pull` (chunked, hashed, excludes) | shipped | | `vm run --sync -- ` | shipped | | `vm exec`, `vm terminal send/read/wait` (drive anything headlessly) | shipped | +| Cloud terminal URL opener (`cmux open`, `BROWSER`/`GH_BROWSER`, `xdg-open`/`x-www-browser`/`sensible-browser`) | shipped | | `vm dev` (detect/record/replay environment, named workspace) | designed (cloud-project-environments.md) | | `vm push --watch` (fs-event incremental sync) | next | | Browser device authentication (`cmux open-url`, `BROWSER`, OS URL openers; exact terminal projection, acknowledgement, headless fallback) | implemented; updated guest/Mac binaries required | @@ -58,6 +59,16 @@ cmux vm layout apply [--name |--workspace ] [-- The Mac CLI runs that implementation over the exec channel; inside a machine the same verb works locally and toward linked peers (`cmux vm layout … `). Presets are just files (a layout saved on the Mac applies with `--from-saved`). +- **Human browser handoff.** Devbox shells set `BROWSER` and `GH_BROWSER`, and + the common Linux opener names point to the same `cmux open ` wrapper. + The command sends a private request through the daemon's durable notification + stream; the Mac consumes it as a browser pane in the caller's remote pane + only when that terminal has a native Mac mirror attached. Otherwise it prints + `Open this URL: ` and exits 0 so device-login CLIs keep polling. + Explicit Chrome, agent-browser, xdotool, and CUA launches continue to use + the VM desktop on `DISPLAY=:1`. Existing machines need the wrapper and env + hook installed once until a devbox snapshot containing this change is + promoted. - **Author without opening**: `apply` builds a new (or an empty) workspace headlessly; `command`s are typed into login shells so panes survive them and the scrollback shows what ran. `--open` is the only thing that touches the Mac. diff --git a/skills/cmux-cloud-vm/SKILL.md b/skills/cmux-cloud-vm/SKILL.md index 5131908525f3..430623b8e426 100644 --- a/skills/cmux-cloud-vm/SKILL.md +++ b/skills/cmux-cloud-vm/SKILL.md @@ -138,6 +138,20 @@ Agents started with `vm agent` authenticate inside the machine the way they woul ## Guest auth and CodeRouter +### Browser opens from cloud terminals + +Devbox login shells route `BROWSER` and `GH_BROWSER`, plus `xdg-open`, +`x-www-browser`, and `sensible-browser`, through `cmux open `. The guest +sends a private request over the daemon's durable notification stream. When the +caller terminal has a native Mac mirror, the Mac consumes that request as a +browser tab in the terminal's remote pane without changing the selected +workspace. With no attached host (for +example `vm exec` or a detached `vm agent`), it prints `Open this URL: ` +and exits successfully so the calling CLI keeps polling. Chrome, +agent-browser, xdotool, and CUA continue to use the VM's `DISPLAY=:1` desktop. +Older machines require a one-time wrapper/env patch until a snapshot carrying +the opener is promoted. + Start with `cmux self --json` to identify the current machine and `cmux vm ls` to list the team's live machines. The guest reads those through its VM-bound TLS edge without a Mac account token. Host lifecycle verbs still run on the Mac; diff --git a/web/messages/en.json b/web/messages/en.json index f11bc6a15473..089e6393217b 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -4,12 +4,11 @@ "description": "Return to your terminal to finish setup." }, "guestCLI": { - "openURLUsage": "Usage: cmux open-url ", - "openURLInvalid": "A valid HTTP or HTTPS URL is required.", - "openURLFallback": "Open this URL: %s", - "topologyHelp": "Arrange this machine without restarting its terminals:\n cmux workspace new [--name ] [--json]\n cmux workspace rename [--json]\n cmux workspace move --index [--json]\n cmux workspace focus|close [--json]\n cmux workspace close [--json]\n cmux workspace close --workspace [--focus false] [--json]\n cmux tab rename [--json]\n cmux terminal rename [--json]\n cmux tab move --workspace --screen --pane --index [--json]\n cmux pane split [--ratio ] [--json]\n cmux pane swap --other-workspace --other-screen --other-pane [--json]\n cmux pane resize --split --ratio [--json]\n cmux pane|tab focus [--json]\n cmux workspace|pane|tab list [--json]\n\nUse IDs from cmux tree. Indices start at zero. A tab label belongs to one placement;\nterminal rename labels every current placement (no process is restarted).\nUse tab move for a terminal with several views so only the selected placement moves.\nFor a peer: cmux vm ...\nSelector-first daemon syntax remains supported. Layout apply creates a new/empty\nworkspace; use move/swap/resize to rearrange an occupied one. These commands change\nthe daemon layout. On the Mac, open it with cmux vm workspace open .\nClose accepts --focus false for compatibility, without an additional focus action.\n--focus true is unsupported. Revision and idempotency options pass through to the daemon.", + "openHelp": "cmux open — open a URL in the attached Mac browser.\n\nIf no host request can be sent, prints the URL and exits 0 so device-login tools keep polling. Alias: cmux open-url.", + "openUsage": "usage: cmux open ", + "openFallback": "Open this URL: %s", + "topologyHelp": "Arrange this machine without restarting its terminals:\n cmux workspace new [--name ] [--json]\n cmux workspace rename [--json]\n cmux workspace move --index [--json]\n cmux workspace focus|close [--json]\n cmux tab rename [--json]\n cmux terminal rename [--json]\n cmux tab move --workspace --screen --pane --index [--json]\n cmux pane split [--ratio ] [--json]\n cmux pane swap --other-workspace --other-screen --other-pane [--json]\n cmux pane resize --split --ratio [--json]\n cmux pane|tab focus [--json]\n cmux workspace|pane|tab list [--json]\n\nUse IDs from cmux tree. Indices start at zero. A tab label belongs to one placement;\nterminal rename labels every current placement (no process is restarted).\nUse tab move for a terminal with several views so only the selected placement moves.\nFor a peer: cmux vm ...\nSelector-first daemon syntax remains supported. Layout apply creates a new/empty\nworkspace; use move/swap/resize to rearrange an occupied one. These commands change\nthe daemon layout. On the Mac, open it with cmux vm workspace open .", "topologyUsage": "Invalid topology arguments. Run cmux workspace help for workspace, pane, tab, and terminal naming and layout commands.", - "topologyCloseFocus": "workspace close accepts --focus false only; use workspace focus for an explicit focus action.", "topologyRenameTarget": "Cannot rename terminal %s: no exact terminal placements or versioned snapshot. Run cmux tree and use a terminal ID.", "topologyRenamePartial": "Terminal %s rename stopped after %s placement(s). Earlier changes remain; inspect cmux tree before retrying.", "topologyRenameReceipt": "Terminal %s returned an invalid or changed-generation rename receipt. Some labels may have changed; inspect cmux tree before retrying.", @@ -98,7 +97,7 @@ "hostTokens": "Stack account tokens: host-only (never stored in this VM)", "version": "cmux guest CLI (cmux-tui transport)", "connected": "OK connected %s socket=%s", - "help": "cmux - Cloud workspace CLI (inside a cmux Cloud machine)\n\nTHIS MACHINE (your session; the same verbs as on a Mac)\n cmux tree [--json] workspaces, panes, and terminals here\n cmux new-workspace [--name ] cmux new-split [--pane ]\n cmux send [--terminal ] cmux send-key [--terminal ] [key...]\n cmux read-screen [--terminal ] [--json]\n cmux terminal send|read|wait|wait-exit|output|close ... drive any terminal headlessly (cmux terminal help)\n cmux layout export [--workspace ] [--raw]\n cmux layout apply [--workspace |--name ] [--cwd ] [|-]\n cmux env set|ls|rm|path environment for every shell and agent here (cmux env help)\n cmux file receive [--mode ] receiving end of cmux vm push (a Mac's --secret, a peer): bytes typed over the link (cmux file help)\n cmux workspace|pane|tab|terminal|browser|session ... cmux-tui's own resource grammar (cmux --help)\n\nWHO AM I\n cmux self [--json] name, id, status, team, owner, plan, as the control plane knows it\n cmux self peers|integrations|owner|machine [--json] what I can reach and use (aliases: cmux whoami, cmux reflect)\n\nOTHER MACHINES (cmux vm ...; the same grammar as on a Mac)\n cmux vm ls [--json] the owner's machines, this one marked *\n cmux vm exec -- cmux vm tree \n cmux vm terminal send|read|wait|wait-exit|output|close ...\n cmux vm workspace new|rename|close|rm ... cmux vm layout export|apply ...\n cmux vm push [--mode ] one file, typed over the link (never an exec API)\n cmux vm agent --agent [--wait [--output] [--timeout ]] -- \n cmux vm env set|ls|rm|path ... (cmux vm help lists them all)\n\nREACH THE HUMAN\n cmux notify --title [--subtitle ] [--body ]\n\nMODELS AND AGENTS\n cmux coderouter status|usage [--json]|models\n cmux coderouter accounts [--json]\n cmux coderouter org current [--json]\n cmux coderouter agent [args...]\n cmux agent [--timeout ] [args...] runs here, in this terminal, until it exits\n cmux agent list|report|hook ...\n\nAUTH\n cmux auth status [--json] this daemon, the injected CodeRouter edge, and the machine identity\n cmux auth login|logout account sign-in is host-owned; run it on the Mac\n\nNo account token lives in this machine: the TLS edge authenticates it on the wire.\n\nNames and existing layouts: cmux workspace help (workspace/tab/terminal rename, tab move, pane split/swap/resize/focus).", + "help": "cmux - Cloud workspace CLI (inside a cmux Cloud machine)\n\nTHIS MACHINE (your session; the same verbs as on a Mac)\n cmux tree [--json] workspaces, panes, and terminals here\n cmux new-workspace [--name ] cmux new-split [--pane ]\n cmux send [--terminal ] cmux send-key [--terminal ] [key...]\n cmux read-screen [--terminal ] [--json]\n cmux terminal send|read|wait|wait-exit|output|close ... drive any terminal headlessly (cmux terminal help)\n cmux layout export [--workspace ] [--raw]\n cmux layout apply [--workspace |--name ] [--cwd ] [|-]\n cmux env set|ls|rm|path environment for every shell and agent here (cmux env help)\n cmux file receive [--mode ] receiving end of cmux vm push (a Mac's --secret, a peer): bytes typed over the link (cmux file help)\n cmux workspace|pane|tab|terminal|browser|session ... cmux-tui's own resource grammar (cmux --help)\n\nWHO AM I\n cmux self [--json] name, id, status, team, owner, plan, as the control plane knows it\n cmux self peers|integrations|owner|machine [--json] what I can reach and use (aliases: cmux whoami, cmux reflect)\n\nOTHER MACHINES (cmux vm ...; the same grammar as on a Mac)\n cmux vm ls [--json] the owner's machines, this one marked *\n cmux vm exec -- cmux vm tree \n cmux vm terminal send|read|wait|wait-exit|output|close ...\n cmux vm workspace new|rename|close|rm ... cmux vm layout export|apply ...\n cmux vm push [--mode ] one file, typed over the link (never an exec API)\n cmux vm agent --agent [--wait [--output] [--timeout ]] -- \n cmux vm env set|ls|rm|path ... (cmux vm help lists them all)\n\nREACH THE HUMAN\n cmux open open in the attached Mac browser, or print the URL when unavailable\n cmux notify --title [--subtitle ] [--body ]\n\nMODELS AND AGENTS\n cmux coderouter status|usage [--json]|models\n cmux coderouter agent [args...]\n cmux agent [--timeout ] [args...] runs here, in this terminal, until it exits\n cmux agent list|report|hook ...\n\nAUTH\n cmux auth status [--json] this daemon, the injected CodeRouter edge, and the machine identity\n cmux auth login|logout account sign-in is host-owned; run it on the Mac\n\nNo account token lives in this machine: the TLS edge authenticates it on the wire.\n\nNames and existing layouts: cmux workspace help (workspace/tab/terminal rename, tab move, pane split/swap/resize/focus).", "peerHelp": "cmux vm - other cmux Cloud machines of the same owner, from inside this one (the same grammar as on a Mac).\n\n cmux vm ls [--json] the owner's machines: this one marked *, status, reachable, linked/connected\n cmux vm connect bring up the link (done lazily otherwise)\n cmux vm exec -- run a command on the machine (durable terminal)\n cmux vm tree the machine's workspace/terminal snapshot\n cmux vm terminal send|read|wait|wait-exit|output|close … drive a terminal there headlessly\n cmux vm send|send-key|read-screen … the same, Mac spelling\n cmux vm workspace new|rename|close|rm … its workspaces (rm kills their terminals)\n cmux vm push [--mode ]\n one file (up to 256 KiB) into its cmux file receive, typed over the link:\n never an exec API, an argv, or a control plane\n cmux vm agent --agent [--name ] [--cwd ] [--workspace ] [--wait [--output] [--timeout ]] -- \n start a coding agent in a durable terminal there; --wait blocks until it exits\n (exit code = the agent's), --output prints its terminal stream, --timeout caps the wait\n cmux vm layout export|apply … its workspace layouts as data (see cmux layout help)\n cmux vm env set|ls|rm|path … its machine env (values travel only over the link; see cmux env help)\n cmux vm … any cmux-tui resource verb there\n\nA machine with no link yet is found through reflection (cmux self peers): its\nprivate-network route is enough on current images, so no Mac step is needed.\nLocal verbs need no prefix: cmux workspace…, cmux terminal…, cmux session….\n\nNames and existing layouts: cmux workspace help (workspace/tab/terminal rename, tab move, pane split/swap/resize/focus).", "thisMachine": "(this machine)", "selfHelp": "cmux self - who am I. Reflection is the control plane's view of this machine, asserted by the TLS edge (no credential lives here).\n\n cmux self [--json] name, id, status, team, owner, plan\n cmux self peers [--json] the owner's other machines: status, private route, reachable\n cmux self integrations [--json] what this machine can use, each with a help command\n cmux self owner|machine [--json] the owner / this machine's size, image, network, desktop\n cmux self [--json] any reflection path (peers, /peers, ...)\n\nAliases: cmux whoami = cmux self; cmux reflect [] = cmux self [].\nDiscovery: cmux vm ls lists the same machines; cmux vm exec -- links on first use.", @@ -136,11 +135,6 @@ "expiredBody": "{machine} and everything on it are fine — only this link's session ended. Reopen the desktop from cmux (right-click the machine, then Open Desktop) to get a fresh one." }, "vmErrors": { - "artifactUnavailable": { - "title": "Cloud VM setup unavailable", - "message": "Cloud VM setup is temporarily unavailable.", - "action": "Try again later. If the problem continues, contact support." - }, "unsupported": { "title": "Cloud VM operation unavailable", "reason": "This machine kind does not provide this operation.", @@ -171,36 +165,8 @@ "title": "cmux Pro required", "message": "Cloud VMs require a cmux Pro plan.", "action": "Upgrade to cmux Pro at {upgradeUrl} to create Cloud VMs." - }, - "displayName": { - "title": "Invalid machine name", - "message": "Machine names must be printable text of at most {maxLength} characters.", - "action": "Use a shorter name without control characters, or leave it empty to clear it." - }, - "memoryPlan": { - "title": "cmux {plan}", - "message": "{memory} GiB machines need cmux {plan}. Your plan supports up to {max} GiB.", - "action": "cmux billing checkout --plan {planId}\ncmux vm new --size {max}g\n{upgradeUrl}" - }, - "memoryUnknown": { - "message": "The source machine’s RAM size could not be verified.", - "action": "cmux vm new --size 4g" - }, - "memoryUnavailable": { - "message": "This machine size is currently unavailable. Choose a size up to {max} GiB.", - "action": "cmux vm new --size {max}g" } - , - "goLimit": { - "savedMessage": "Go includes two saved VMs, including the running VM.", - "savedAction": "cmux vm ls\ncmux vm rm \ncmux billing checkout --plan pro", - "activeMessage": "Go includes one running VM at a time.", - "activeAction": "cmux vm pause \ncmux billing checkout --plan pro", - "hoursMessage": "Go has used its 40 VM-hours this month. Saved VMs remain available after the next billing period starts.", - "hoursAction": "cmux billing checkout --plan pro", - "shapeMessage": "Go supports 2 vCPU, 4 GiB RAM, and 16 GiB disk.", - "shapeAction": "cmux vm new --size 4g\ncmux billing checkout --plan pro" - }}, + }, "ios": { "metaTitle": "cmux iOS — your terminals on iPhone and iPad", "metaDescription": "The cmux iOS app mirrors your Mac terminals to iPhone and iPad in realtime, over your own Tailscale or local network. Drive your coding agents from anywhere.", @@ -326,8 +292,7 @@ "accountGroup": "account", "billing": "billing", "team": "team", - "testflight": "iOS TestFlight", - "irohDevices": "IROH devices" + "testflight": "iOS TestFlight" }, "home": { "title": "dashboard", @@ -389,7 +354,7 @@ }, "coderouterAccounts": { "title": "Accounts", - "description": "Shared accounts are available to this team’s Cloud VMs. Private accounts stay with the person who added them.", + "description": "Claude Code, Codex, and OpenCode on this team's Cloud VMs and CLI route through these accounts. Each machine is pinned to one account per provider so prompt caching keeps working. A rate limit or a rejected credential puts that account in cooldown and moves the machine to the next one.", "accountsCount": "{count, plural, one {# account} other {# accounts}}", "emptyTitle": "No accounts yet", "emptyBody": "Requests from this team fail until an account is added.", @@ -461,10 +426,7 @@ "removeClaudeConfirmBody": "Machines pinned to it move to the team's other accounts. If it is the last one, Claude requests from this team fail until a new account is added.", "removeNativeConfirmBody": "Sessions pinned to it move to the team's other accounts. Adding it back means signing in again with cr add.", "removeSharedConfirmBody": "This removes the account for the whole team. Adding it back requires signing in again.", - "cancelAction": "Cancel", - "shareWithTeam": "Share with team", - "makePrivate": "Make private", - "privateImportHint": "New accounts are private. Share an account to make it available to this team’s Cloud VMs." + "cancelAction": "Cancel" }, "accountMenu": { "label": "Account menu", @@ -509,12 +471,6 @@ "annualPrice": "${monthly}/mo, billed annually", "grantedBody": "Pro access was granted to this account by the cmux team. There is no subscription to manage." }, - "go": { - "name": "cmux Go", - "activeBody": "Your Go plan is active. It renews on {date}.", - "pendingBody": "Your Go plan ends on {date}.", - "monthlyPrice": "${amount}/mo" - }, "team": { "name": "cmux Team", "fallbackName": "your team", @@ -549,13 +505,6 @@ "free": "Free", "pro": "Pro", "loading": "Loading…" - }, - "max": { - "name": "cmux Max", - "activeBody": "Your Max plan is active. It renews on {date}.", - "pendingBody": "Your Max plan ends on {date}.", - "monthlyPrice": "${amount}/mo", - "upsell": "Run machines with up to 64 GB RAM on Max for $200 per month, billed monthly." } }, "testflight": { @@ -601,24 +550,6 @@ "join": "Join iOS TestFlight", "leave": "Leave TestFlight" } - }, - "iroh": { - "title": "IROH devices", - "description": "Manage devices in the selected Stack team. Access is filtered by team permissions.", - "team": "Team", - "loading": "Loading devices…", - "empty": "No IROH devices are registered for this team.", - "unavailable": "Dashboard authorization is unavailable.", - "mutationError": "The device change could not be completed.", - "revoke": "Revoke access", - "deviceId": "Device", - "revision": "Revision", - "status": "Status", - "active": "Active", - "revoked": "Revoked", - "relaySettings": "Relay settings", - "relaySettingsDescription": "Choose the relay servers used by this team. Enter one URL per line.", - "saveRelaySettings": "Save relay settings" } }, "vault": { @@ -858,22 +789,18 @@ "metaDescriptionNoVault": "cmux is a free, open source macOS terminal for AI coding agents. Upgrade to Pro for isolated Cloud VMs. Enterprise adds SSO and self-hosting.", "metaDescriptionNoVaultShort": "cmux is a free, open source macOS terminal. Pro adds isolated Cloud VMs; Enterprise adds SSO and self-hosting.", "title": "Pricing", - "categories": { - "individual": { - "title": "For individuals", - "description": "Start free, then add cloud capacity as your work grows." - }, - "business": { - "title": "For teams and businesses", - "description": "Shared billing, administration, and controls for your organization." - } - }, "alreadyPaid": "Already paid? Connect Founder's Edition", "perMonth": "/mo", + "perMonthBilledYearly": "/mo, billed yearly", "perUserMonth": "/user/mo", + "perUserMonthBilledYearly": "/user/mo, billed yearly", "billingPeriod": "Billing period", "monthly": "Monthly", + "annual": "Annual", + "saveAnnual": "Save {discount}%", + "annualComparePrice": "${monthly}/mo", "teamMonthlyComparePrice": "${monthly}/user/mo", + "teamAnnualComparePrice": "${monthly}/user/mo", "currentPlan": "Current plan", "manageBilling": "Manage billing", "free": { @@ -890,17 +817,6 @@ "Community support on Discord and GitHub" ] }, - "go": { - "name": "Go", - "cta": "Get Go", - "featuresLead": "A small Cloud VM for focused work:", - "features": [ - "1 active Cloud VM", - "2 vCPU, 4 GiB RAM, and 16 GiB disk", - "2 saved VMs in total, including the running VM", - "40 included VM-hours each month; VMs pause at the limit" - ] - }, "pro": { "name": "Pro", "cta": "Get Pro", @@ -919,15 +835,6 @@ "Hosted networking to reach your Mac from the iOS app, no Tailscale setup" ] }, - "max": { - "name": "Max", - "cta": "Get Max", - "featuresLead": "Everything in Pro, plus:", - "features": [ - "Up to 64 GB RAM per machine", - "Everything in Pro: 50 Cloud VMs, unlimited workspaces, the iOS app" - ] - }, "team": { "name": "Team", "cta": "Get Teams", @@ -958,176 +865,131 @@ "label": "Native macOS terminal, open source", "free": "true", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "Local CLI agents with your own keys", "free": "true", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "Vertical tabs, splits, notifications, socket API", "free": "true", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "Session history", "free": "Local", "pro": "Unlimited", - "max": "Unlimited", "team": "Unlimited", "enterprise": "Unlimited", - "vault": true, - "go": "Local" + "vault": true }, { "label": "cmux Vault: cloud session backup", "free": "false", "pro": "true", - "max": "true", "team": "true", "enterprise": "true", - "vault": true, - "go": "false" + "vault": true }, { "label": "Search across all your sessions", "free": "false", "pro": "true", - "max": "true", "team": "true", "enterprise": "true", - "vault": true, - "go": "false" + "vault": true }, { "label": "Cloud agents on Cloud VMs", "free": "false", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "Concurrent Cloud VMs", "free": "false", "pro": "50", - "max": "50", "team": "50 per user", - "enterprise": "Custom", - "go": "1" + "enterprise": "Custom" }, { "label": "Resources shared across all Cloud VMs", "free": "false", "pro": "24 GB RAM and 6 vCPUs shared across all VMs", - "max": "64 GB RAM and 16 vCPUs per VM", "team": "24 GB RAM and 6 vCPUs per user, shared across all their VMs", - "enterprise": "Custom", - "go": "2 vCPU, 4 GiB RAM, 16 GiB disk" - }, - { - "label": "Largest Cloud VM", - "free": "24 GB RAM", - "pro": "24 GB RAM", - "max": "64 GB RAM", - "team": "24 GB RAM", - "enterprise": "Custom", - "go": "4 GiB RAM" + "enterprise": "Custom" }, { "label": "Workspaces", "free": "Unlimited", "pro": "Unlimited", - "max": "Unlimited", "team": "Unlimited", - "enterprise": "Unlimited", - "go": "Unlimited" + "enterprise": "Unlimited" }, { "label": "iOS app", "free": "false", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "Networking to reach your Mac", "free": "Bring your own", "pro": "Hosted", - "max": "Hosted", "team": "Hosted", "enterprise": "Self-hosted", - "hostedNetworking": true, - "go": "Bring your own" + "hostedNetworking": true }, { "label": "SSO and SAML sign-in", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "Self-hosted and air-gapped execution", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "Audit logs: sessions, network, browser", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "Volume pricing", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "SOC 2 and an SLA", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "Support", "free": "Community", "pro": "Email", - "max": "Email", "team": "Priority", - "enterprise": "Dedicated", - "go": "Community" + "enterprise": "Dedicated" } ] }, @@ -1141,11 +1003,7 @@ }, { "q": "What are Cloud VMs?", - "a": "Persistent Linux machines in the cloud: pets, not cattle. Following the Zen of cmux, a machine is a primitive, not a managed solution: it keeps its files, installed tools, and state, and what you build on it is yours. Agents clone repositories, run commands, and open pull requests there without touching your Mac. Pro includes up to 50 Cloud VMs with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same pool per user." - }, - { - "q": "What does Max add?", - "a": "Max is $200/mo, billed monthly, with up to 64 GB RAM and 16 vCPUs per VM. It includes everything in Pro and unlocks Cloud VMs with 32 GB or 64 GB RAM. Pro, Team, and Free machines top out at 24 GB RAM." + "a": "Persistent Linux machines in the cloud: pets, not cattle. Following the Zen of cmux, a machine is a primitive, not a managed solution: it keeps its files, installed tools, and state, and what you build on it is yours. Agents clone repositories, run commands, and open pull requests there without touching your Mac. Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." }, { "q": "What is cmux Vault?", @@ -1158,7 +1016,7 @@ }, { "q": "How does billing work?", - "a": "Paid plans are billed monthly. Pro is $50/mo, Max is $200/mo, and Team is $60/user/mo. The terminal and local agents stay free." + "a": "Pro is $50/mo, or $40/mo when billed annually (a 20% savings). Team is $60/user/mo, or $48/user/mo when billed annually. Paid plans include up to 50 Cloud VMs per user, with 24 GB RAM and 6 vCPUs per user shared across all their VMs. There is no metering or overage billing. The terminal and local agents stay free." }, { "q": "Can I self-host the cloud?", @@ -1166,7 +1024,7 @@ }, { "q": "Do you have a team plan?", - "a": "Yes. Team is $60/user/mo with centralized billing for your whole team and priority support." + "a": "Yes. Team is $60/user/mo, or $48/user/mo when billed annually, with centralized billing for your whole team and priority support." } ] }, @@ -1177,20 +1035,11 @@ "billingError": "Something went wrong starting checkout. Please try again in a moment.", "billingUnavailable": "Billing is not available right now. Please try again later.", "billingCancelled": "Checkout cancelled. You have not been charged.", - "billingAnnualUnavailable": "Annual billing is not available for new subscriptions. Choose a monthly plan below.", - "billingPlanUnavailable": "This plan is not available yet. Choose Pro or Max below.", "billingInvalidPlan": "That plan is not available. Pick a plan below.", "signedOutNotice": "You're not signed in. Sign in to see your current plan or restore an existing purchase.", "signedOutSignIn": "Sign in", "billingInvalidRelay": "The app checkout link expired or could not be verified. Return to cmux and try again.", - "welcomeTeam": "Your cmux Team purchase is complete.", - "audience": { - "label": "Pricing audience", - "individual": "Individual", - "team": "Team & Enterprise" - }, - "priceMonthlyNote": "${amount}/mo when billed monthly", - "priceMonthlyPerUserNote": "${amount}/user/mo when billed monthly" + "welcomeTeam": "Your cmux Team purchase is complete." }, "home": { "taglinePrefix": "The terminal built for ", @@ -1961,60 +1810,6 @@ } }, "docs": { - "computerUse": { - "title": "cmux Computer Use", - "metaTitle": "cmux Computer Use: AI agents for Mac automation", - "metaDescription": "Let Claude Code and Codex control your Mac with cmux Computer Use. Automate native apps with screenshots, accessibility, clicks, typing, and keyboard input.", - "docsMetaTitle": "Computer Use setup and agent tools", - "docsMetaDescription": "Set up cmux Computer Use on macOS. Learn permissions, Claude Code and Codex tools, app targeting, session controls, skills, and troubleshooting.", - "tagline": "Let Claude Code and Codex control your Mac.", - "intro": "Give AI coding agents access to native Mac apps. Read app interfaces, take screenshots, click controls, and type text with cmux Computer Use.", - "imageAlt": "cmux Computer Use setup with Accessibility and Screenshots permission controls", - "caption": "Computer Use permissions are managed by a separate helper, so your terminal sessions stay open during onboarding.", - "featuresTitle": "Control native Mac apps", - "featureInspect": "Inspect app interfaces with accessibility trees and screenshots.", - "featureAct": "Click, type, scroll, drag, and use keyboard shortcuts in macOS apps.", - "featureReview": "Follow the agent’s visible cursor, inspect the result, and return to its calling terminal.", - "agentsTitle": "Your coding agents in cmux", - "agentsBody": "Run {agents} and other command-line agents in cmux.", - "supportTitle": "Supported agents", - "supportBody": "Native Computer Use tools attach automatically to Claude Code and Codex sessions launched inside cmux. Other agents can run in the terminal, but do not yet receive this automatic Mac-control integration.", - "setupTitle": "Get started", - "step1": "Install and open cmux for Mac. In Settings → Computer Use, enable Computer Use.", - "step2": "Use the permission controls to grant Accessibility and Screen Recording to cmux Computer Use in System Settings. If the helper is missing from the list, add or drag its app tile into the permission pane.", - "step3": "Start a new Claude Code or Codex session inside cmux. Ask it to use Computer Use and name the app and task. The tools connect automatically; no manual MCP configuration is required.", - "permissionsTitle": "Permissions and setup", - "permissionsBody": "Accessibility allows the helper to read and interact with app interfaces. Screen Recording allows it to capture screenshots. Grant both permissions to cmux Computer Use. Opening cmux or starting an agent does not grant access or open setup automatically.", - "tahoeBody": "On macOS Tahoe, approve the additional screen-capture confirmation during setup. Wait until cmux shows both permissions as granted before retrying the task.", - "promptTitle": "Try it with Calculator", - "prompt": "Use cmux Computer Use to open Calculator, click 100 + 105, and show me the result.", - "workflowBody": "Inspect the target first, act on controls from that snapshot, then inspect again to verify the result. Element indexes belong to the current snapshot; refresh them after navigation or layout changes. A successful click acknowledgement alone does not prove the task finished.", - "toolsTitle": "Agent tools", - "toolsBody": "cmux-cua is the bundled MCP server, which gives agents structured tools for native app control. These are tool names, not shell commands. The exact arguments depend on the agent profile; use the schemas attached to the session.", - "codexBody": "Codex uses get_app_state with an app name or bundle identifier to receive a screenshot and accessibility tree. Act on a current element_index or screenshot coordinates, then call get_app_state again. Its tool set is:", - "claudeBody": "Claude Code uses list_apps and list_windows to find a target, then get_window_state with its pid and window_id. Prefer element tokens or fresh indexes. The native profile also supports perform_actions for a group of actions that does not invalidate later targets.", - "controlTitle": "Follow and stop Computer Use", - "controlBody": "The Computer Use menu-bar item shows the active agent. Focus Computer Use follows the app it is driving; Focus Calling Terminal returns to the originating terminal. Returning to the terminal does not stop automation. Use the menu’s stop action to stop the driving session.", - "disableBody": "Turn off Computer Use in Settings to disable it. To exclude the tools from a new agent launch, set CMUX_COMPUTER_USE_MCP_DISABLED=1 before launching that agent.", - "skillTitle": "Skills", - "skillBody": "The cmux-cua skill explains the inspection and action workflow. It is optional and does not grant macOS permissions. Use your agent’s skill installer for a persistent installation; cmux does not install global skills by default.", - "skillLink": "Read the cmux-cua skill", - "browserTitle": "Native apps and browser automation", - "browserBody": "Computer Use operates native Mac windows through screenshots and accessibility. For cmux browser panes, use cmux browser commands for DOM snapshots, selectors, forms, and JavaScript. A browser surface reference is different from a native app element index.", - "browserLink": "Browser automation reference", - "troubleTitle": "Troubleshooting", - "missingTitle": "The agent has no Computer Use tools", - "missingBody": "Enable Computer Use in cmux Settings and start a fresh Claude Code or Codex session inside cmux. Existing sessions may retain their original tool configuration. Check that Computer Use is not disabled by an environment variable or a managed policy.", - "blockedTitle": "Setup is incomplete, clicks fail, or screenshots are blank", - "blockedBody": "Reopen Settings → Computer Use and finish both permission steps, including any extra capture confirmation. Retry after setup completes. If the helper needs to restart, your terminal sessions can remain open.", - "targetTitle": "The wrong window is targeted or a click is blocked", - "targetBody": "Inspect the app again and select a fresh element from its current state. If another window covers the target, reveal the intended window and inspect again before retrying. Do not reuse stale coordinates or element indexes.", - "productLink": "cmux Computer Use overview", - "copyTitle": "Copy a Computer Use prompt", - "copyPrompt": "Copy prompt", - "copiedPrompt": "Copied", - "installPrompt": "Use the $cmux-cua skill for this task. If it is unavailable, install it with your normal skill installer. If permissions are missing, ask me to open cmux Settings → Computer Use. Inspect before acting and verify after each change.\n\nTask: [describe the task here]" - }, "base": { "title": "Base", "metaTitle": "Base cloud workspace", @@ -2440,11 +2235,10 @@ "warnBeforeClosingTabXButton": "Show a confirmation before closing a tab with the tab close button.", "hideTabCloseButton": "Hide tab close buttons in the pane tab bar.", "forkConversationDefaultDestination": "Default destination for the tab context menu's primary Fork Conversation action. The submenu still exposes every destination.", - "globalFontMagnification": "Scales cmux-owned terminals, tab titles, sidebars, settings, overlays, and app chrome by this percentage. Rendered browser page content is excluded.", - "paneResizeStepPixels": "Pixels moved per keypress when using pane-resize shortcuts. Accepts 1–200; defaults to 20. Changes apply immediately." + "globalFontMagnification": "Scales cmux-owned terminals, tab titles, sidebars, settings, overlays, and app chrome by this percentage. Rendered browser page content is excluded." }, "terminal": { - "adaptiveDefaultTheme": "When true (the default), cmux supplies an appearance-adaptive default palette unless a Ghostty theme or terminal colors are configured. Font and other settings are preserved. When false, Ghostty uses its fixed built-in palette. Explicit themes and colors always take precedence, including theme = light:X,dark:Y.", + "adaptiveDefaultTheme": "When true (the default), cmux supplies its managed light/dark palette only when the Ghostty config contains no directives. When false, an untouched Ghostty config uses Ghostty's fixed built-in palette. Any Ghostty directive suppresses the managed palette; Ghostty theme = light:X,dark:Y remains appearance-adaptive.", "agentHibernation": "Routine Agent Hibernation settings. cmux kills idle background agent processes to free RAM and CPU, then resumes them with their saved session when their tab is visited. Routine hibernation requires a restorable coding agent whose lifecycle reports idle, an off-screen terminal, a live-terminal count above the configured limit, and unchanged output through the idle and confirmation windows. Independently, during critical memory pressure cmux may hibernate a bounded batch of safe idle background agents even when enabled is false; visible, running, needs-input, recently changed, and unprotectable agents remain excluded. The placeholder Resume button is a manual fallback.", "agentHibernationEnabled": "Enable routine Agent Hibernation based on the live-terminal limit. Critical-pressure safety hibernation remains active when false.", "copyOnSelect": "When true, copy selected terminal text to the system clipboard when the selection is committed. When false, cmux does not emit a Ghostty copy-on-select override; Ghostty config and defaults control selection-clipboard behavior.", @@ -2586,7 +2380,7 @@ "plusButtonScreenshotAlt": "cmux plus button right-click menu showing Codex Worktree, New Terminal, and New Browser", "plusButtonScreenshotCaption": "The right-click menu follows the order from ui.newWorkspace.contextMenu.", "newWorkspaceButton": "Custom plus button actions", - "newWorkspaceButtonDesc": "Use ui.newWorkspace.action to override what the plus button does. Use ui.newWorkspace.contextMenu (or the rightClick alias) to define the ordered right-click menu. Menu entries can be action IDs, action objects, or { \"type\": \"separator\" }. When it is not set, the menu lists cmux.newWorkspace (⌘N), cmux.newCloudWorkspace (⇧⌘Y), cmux.newCloudMachine (⌘Y), cmux.newTerminal (⌘T), cmux.newBrowser (⇧⌘L), each showing its current shortcut. Cloud rows appear only when Cloud Machines is enabled.", + "newWorkspaceButtonDesc": "Use ui.newWorkspace.action to override what the plus button does. Use ui.newWorkspace.contextMenu (or the rightClick alias) to define the ordered right-click menu. Menu entries can be action IDs, action objects, or { \"type\": \"separator\" }.", "newWorkspaceWorktreeNote": "This example makes the normal plus click run the worktree-agents action. The workspace command from commands uses a visible setup terminal to create the Git worktree first. Codex and Claude start at the same time, wait for the workspace-specific state file, then cd into the created directory before exec.", "workspaceActions": "Workspace layouts", "workspaceActionsDesc": "A workspace layout is stored as a workspace action, embedding a full workspace definition directly in the action so it needs no separate commands entry. It supports the same workspace fields plus setup, a bootstrap command sent to the first terminal before that terminal's own command:", @@ -2718,8 +2512,7 @@ "wn-settings": "Settings", "wn-reload": "Reload configuration", "wn-quit": "Quit" - }, - "paneResizeDescription": "Resize the focused pane with Ctrl+Shift+H/J/K/L (left/down/up/right). Hold a key to keep resizing. Change the bindings and Pane Resize Step in Settings → Keyboard Shortcuts. The step is also configurable with app.paneResizeStepPixels in cmux.json (1–200, default 20)." + } }, "api": { "title": "CLI Reference", @@ -3287,7 +3080,6 @@ "envSocket": "Path to the cmux control socket for the shim to connect to" }, "navItems": { - "computerUse": "Computer Use", "gettingStarted": "Getting Started", "tui": "cmux TUI", "concepts": "Concepts", @@ -5731,12 +5523,5 @@ "message": "Publication email grant", "action": "Send email and optional expiresAt." } - }, - "cliAuthIdentity": { - "email": "Email", - "emailUnavailable": "email unavailable", - "organization": "Organization", - "personalAccount": "personal account", - "switchAccountButton": "Use a different account" } } diff --git a/web/messages/ja.json b/web/messages/ja.json index b27eb7144ecb..d19ad6a6d5ed 100644 --- a/web/messages/ja.json +++ b/web/messages/ja.json @@ -4,12 +4,11 @@ "description": "ターミナルに戻ってセットアップを完了してください。" }, "guestCLI": { - "openURLUsage": "使い方: cmux open-url ", - "openURLInvalid": "有効な HTTP または HTTPS の URL が必要です。", - "openURLFallback": "この URL を開いてください: %s", - "topologyHelp": "ターミナルを再起動せずに、このマシンの配置を変更します:\n cmux workspace new [--name ] [--json]\n cmux workspace rename [--json]\n cmux workspace move --index [--json]\n cmux workspace focus|close [--json]\n cmux workspace close [--json]\n cmux workspace close --workspace [--focus false] [--json]\n cmux tab rename [--json]\n cmux terminal rename [--json]\n cmux tab move --workspace --screen --pane --index [--json]\n cmux pane split [--ratio ] [--json]\n cmux pane swap --other-workspace --other-screen --other-pane [--json]\n cmux pane resize --split --ratio [--json]\n cmux pane|tab focus [--json]\n cmux workspace|pane|tab list [--json]\n\nID は cmux tree で確認できます。インデックスは 0 から始まります。\nタブ名は配置ごとに設定され、terminal rename はそのターミナルの現在の全配置を変更します。\n複数の表示があるターミナルは tab move で対象の配置だけを移動してください。\nピアでは cmux vm ... を使います。\nセレクターを先に指定するデーモン構文も利用できます。layout apply は新規または空の\nワークスペース用です。使用中の配置は move/swap/resize で変更してください。\nこれらはデーモンのレイアウトを変更します。Mac で表示するには\ncmux vm workspace open を使います。\nclose は互換性のため --focus false を受け付けますが、追加のフォーカス操作は行いません。\n--focus true は未対応です。リビジョンと冪等性のオプションはデーモンへそのまま渡されます。", + "openHelp": "cmux open — 接続中の Mac のブラウザーで URL を開きます。\n\nホストへのリクエストを送れない場合は URL を表示し、終了コード 0 を返します。デバイスログインの待機は継続されます。別名: cmux open-url。", + "openUsage": "使用方法: cmux open ", + "openFallback": "この URL を開いてください: %s", + "topologyHelp": "ターミナルを再起動せずに、このマシンの配置を変更します:\n cmux workspace new [--name ] [--json]\n cmux workspace rename [--json]\n cmux workspace move --index [--json]\n cmux workspace focus|close [--json]\n cmux tab rename [--json]\n cmux terminal rename [--json]\n cmux tab move --workspace --screen --pane --index [--json]\n cmux pane split [--ratio ] [--json]\n cmux pane swap --other-workspace --other-screen --other-pane [--json]\n cmux pane resize --split --ratio [--json]\n cmux pane|tab focus [--json]\n cmux workspace|pane|tab list [--json]\n\nID は cmux tree で確認できます。インデックスは 0 から始まります。\nタブ名は配置ごとに設定され、terminal rename はそのターミナルの現在の全配置を変更します。\n複数の表示があるターミナルは tab move で対象の配置だけを移動してください。\nピアでは cmux vm ... を使います。\nセレクターを先に指定するデーモン構文も利用できます。layout apply は新規または空の\nワークスペース用です。使用中の配置は move/swap/resize で変更してください。\nこれらはデーモンのレイアウトを変更します。Mac で表示するには\ncmux vm workspace open を使います。", "topologyUsage": "配置コマンドの引数が無効です。名前変更と配置操作は cmux workspace help で確認してください。", - "topologyCloseFocus": "workspace close では --focus false のみ指定できます。明示的にフォーカスを移すには workspace focus を使ってください。", "topologyRenameTarget": "ターミナル %s を変更できません。対象の配置またはバージョン付きスナップショットがありません。cmux tree のターミナル ID を使ってください。", "topologyRenamePartial": "ターミナル %s の名前変更は %s 個の配置を変更した時点で停止しました。変更済みの名前は残ります。再試行前に cmux tree で確認してください。", "topologyRenameReceipt": "ターミナル %s の名前変更結果が無効、または世代が変わりました。一部の名前は変更済みの可能性があります。再試行前に cmux tree で確認してください。", @@ -98,7 +97,7 @@ "hostTokens": "アカウントの認証情報: ホストのみ(このマシンには保存されません)", "version": "cmux ゲスト CLI(マシン接続)", "connected": "OK %s に接続しました socket=%s", - "help": "cmux - クラウドワークスペース CLI(cmux Cloud マシン内)\n\nこのマシン(自分のセッション。Mac と同じコマンド)\n cmux tree [--json] ワークスペース・ペイン・端末の一覧\n cmux new-workspace [--name ] cmux new-split [--pane ]\n cmux send [--terminal ] cmux send-key [--terminal ] [key...]\n cmux read-screen [--terminal ] [--json]\n cmux terminal send|read|wait|wait-exit|output|close ... 任意の端末をヘッドレスで操作(cmux terminal help)\n cmux layout export [--workspace ] [--raw]\n cmux layout apply [--workspace |--name ] [--cwd ] [|-]\n cmux env set|ls|rm|path このマシンの全シェルとエージェントの環境変数(cmux env help)\n cmux file receive [--mode ] cmux vm push の受信側(Mac の --secret、他マシン)。バイト列はリンク経由で入力(cmux file help)\n cmux workspace|pane|tab|terminal|browser|session ... cmux-tui のリソース文法(cmux --help)\n\n自分は誰か\n cmux self [--json] 名前・ID・状態・チーム・所有者・プラン(コントロールプレーンの把握内容)\n cmux self peers|integrations|owner|machine [--json] 到達できるマシンと使える機能(別名: cmux whoami, cmux reflect)\n\n他のマシン(cmux vm ...。Mac と同じ文法)\n cmux vm ls [--json] 所有者のマシン一覧(このマシンは * 付き)\n cmux vm exec -- cmux vm tree \n cmux vm terminal send|read|wait|wait-exit|output|close ...\n cmux vm workspace new|rename|close|rm ... cmux vm layout export|apply ...\n cmux vm push [--mode ] ファイル 1 つをリンク経由で入力(exec API は通さない)\n cmux vm agent --agent [--wait [--output] [--timeout ]] -- \n cmux vm env set|ls|rm|path ... (一覧は cmux vm help)\n\n人間に知らせる\n cmux notify --title [--subtitle ] [--body ]\n\nモデルとエージェント\n cmux coderouter status|usage [--json]|models\n cmux coderouter accounts [--json]\n cmux coderouter org current [--json]\n cmux coderouter agent [args...]\n cmux agent [--timeout ] [args...] この端末で終了まで実行\n cmux agent list|report|hook ...\n\n認証\n cmux auth status [--json] デーモン・CodeRouter エッジ・マシン識別の確認\n cmux auth login|logout サインインは Mac で管理します\n\nアカウントの認証情報はマシンにコピーされません。TLS エッジが通信上で認証します。\n\n名前と既存の配置の操作: cmux workspace help(workspace/tab/terminal rename、tab move、pane split/swap/resize/focus)。", + "help": "cmux - クラウドワークスペース CLI(cmux Cloud マシン内)\n\nこのマシン(自分のセッション。Mac と同じコマンド)\n cmux tree [--json] ワークスペース・ペイン・端末の一覧\n cmux new-workspace [--name ] cmux new-split [--pane ]\n cmux send [--terminal ] cmux send-key [--terminal ] [key...]\n cmux read-screen [--terminal ] [--json]\n cmux terminal send|read|wait|wait-exit|output|close ... 任意の端末をヘッドレスで操作(cmux terminal help)\n cmux layout export [--workspace ] [--raw]\n cmux layout apply [--workspace |--name ] [--cwd ] [|-]\n cmux env set|ls|rm|path このマシンの全シェルとエージェントの環境変数(cmux env help)\n cmux file receive [--mode ] cmux vm push の受信側(Mac の --secret、他マシン)。バイト列はリンク経由で入力(cmux file help)\n cmux workspace|pane|tab|terminal|browser|session ... cmux-tui のリソース文法(cmux --help)\n\n自分は誰か\n cmux self [--json] 名前・ID・状態・チーム・所有者・プラン(コントロールプレーンの把握内容)\n cmux self peers|integrations|owner|machine [--json] 到達できるマシンと使える機能(別名: cmux whoami, cmux reflect)\n\n他のマシン(cmux vm ...。Mac と同じ文法)\n cmux vm ls [--json] 所有者のマシン一覧(このマシンは * 付き)\n cmux vm exec -- cmux vm tree \n cmux vm terminal send|read|wait|wait-exit|output|close ...\n cmux vm workspace new|rename|close|rm ... cmux vm layout export|apply ...\n cmux vm push [--mode ] ファイル 1 つをリンク経由で入力(exec API は通さない)\n cmux vm agent --agent [--wait [--output] [--timeout ]] -- \n cmux vm env set|ls|rm|path ... (一覧は cmux vm help)\n\n人間に知らせる\n cmux open 接続中の Mac で開く(利用できない場合は URL を表示)\n cmux notify --title [--subtitle ] [--body ]\n\nモデルとエージェント\n cmux coderouter status|usage [--json]|models\n cmux coderouter agent [args...]\n cmux agent [--timeout ] [args...] この端末で終了まで実行\n cmux agent list|report|hook ...\n\n認証\n cmux auth status [--json] デーモン・CodeRouter エッジ・マシン識別の確認\n cmux auth login|logout サインインは Mac で管理します\n\nアカウントの認証情報はマシンにコピーされません。TLS エッジが通信上で認証します。\n\n名前と既存の配置の操作: cmux workspace help(workspace/tab/terminal rename、tab move、pane split/swap/resize/focus)。", "peerHelp": "cmux vm - このマシンから、同じ所有者の他の cmux Cloud マシンを操作(Mac と同じ文法)。\n\n cmux vm ls [--json] 所有者のマシン一覧: このマシンは * 付き、状態、到達可否、linked/connected\n cmux vm connect リンクを開始(他の操作時にも自動接続)\n cmux vm exec -- そのマシンの永続端末でコマンドを実行\n cmux vm tree そのマシンのワークスペースと端末の状態\n cmux vm terminal send|read|wait|wait-exit|output|close … そのマシンの端末を操作\n cmux vm send|send-key|read-screen … Mac と同じコマンド名\n cmux vm workspace new|rename|close|rm … そのマシンのワークスペース(rm は端末も終了)\n cmux vm push [--mode ]\n ファイル 1 つ(最大 256 KiB)をそのマシンの cmux file receive にリンク経由で入力:\n exec API・argv・コントロールプレーンは通りません\n cmux vm agent --agent [--name ] [--cwd ] [--workspace ] [--wait [--output] [--timeout ]] -- \n そのマシンの永続端末でエージェントを開始。--wait は終了まで待機(終了コードはエージェントのもの)、\n --output は端末の出力ストリームを表示、--timeout は待機時間の上限\n cmux vm layout export|apply … そのマシンのレイアウト(cmux layout help を参照)\n cmux vm env set|ls|rm|path … そのマシンの環境変数(値はリンク経由のみ。cmux env help を参照)\n cmux vm … そのマシンの cmux-tui リソース操作\n\nリンクのないマシンはリフレクション(cmux self peers)で見つかります。現在のイメージでは\nプライベートネットワークのルートだけで接続でき、Mac での操作は不要です。\nローカル操作に接頭辞は不要です:cmux workspace…、cmux terminal…、cmux session…。\n\n名前と既存の配置の操作: cmux workspace help(workspace/tab/terminal rename、tab move、pane split/swap/resize/focus)。", "thisMachine": "(このマシン)", "selfHelp": "cmux self - 自分は誰か。リフレクションは TLS エッジが保証する、コントロールプレーンから見たこのマシンの情報です(認証情報はマシン内にありません)。\n\n cmux self [--json] 名前・ID・状態・チーム・所有者・プラン\n cmux self peers [--json] 所有者の他のマシン: 状態・プライベートルート・到達可否\n cmux self integrations [--json] このマシンで使える機能と、そのヘルプコマンド\n cmux self owner|machine [--json] 所有者 / このマシンのサイズ・イメージ・ネットワーク・デスクトップ\n cmux self [--json] 任意のリフレクションパス(peers、/peers など)\n\n別名: cmux whoami = cmux self、cmux reflect [] = cmux self []。\n探索: cmux vm ls は同じマシン一覧を表示し、cmux vm exec -- は初回に自動でリンクします。", @@ -136,11 +135,6 @@ "expiredBody": "{machine} とその中身は無事です。このリンクのセッションだけが終了しました。cmux でマシンを右クリックし、「デスクトップを開く」から新しく開き直してください。" }, "vmErrors": { - "artifactUnavailable": { - "title": "Cloud VM のセットアップを利用できません", - "message": "Cloud VM のセットアップを一時的に利用できません。", - "action": "しばらくしてから再試行してください。問題が続く場合はサポートにお問い合わせください。" - }, "unsupported": { "title": "Cloud VM 操作を利用できません", "reason": "このマシン種別ではこの操作を利用できません。", @@ -171,36 +165,8 @@ "title": "cmux Pro が必要です", "message": "Cloud VM を利用するには cmux Pro プランが必要です。", "action": "{upgradeUrl} で cmux Pro にアップグレードすると Cloud VM を作成できます。" - }, - "displayName": { - "title": "マシン名が無効です", - "message": "マシン名は {maxLength} 文字以内の表示可能なテキストにしてください。", - "action": "制御文字を含まない短い名前を使うか、空にして名前をクリアしてください。" - }, - "memoryPlan": { - "title": "cmux {plan}", - "message": "{memory} GiB のマシンには cmux {plan} が必要です。現在のプランの上限は {max} GiB です。", - "action": "cmux billing checkout --plan {planId}\ncmux vm new --size {max}g\n{upgradeUrl}" - }, - "memoryUnknown": { - "message": "元のマシンの RAM 容量を確認できませんでした。", - "action": "cmux vm new --size 4g" - }, - "memoryUnavailable": { - "message": "このマシンサイズは現在利用できません。{max} GiB 以下を選択してください。", - "action": "cmux vm new --size {max}g" } - , - "goLimit": { - "savedMessage": "Go では実行中の VM を含めて 2 台まで保存できます。", - "savedAction": "cmux vm ls\ncmux vm rm \ncmux billing checkout --plan pro", - "activeMessage": "Go では同時に 1 台の VM を実行できます。", - "activeAction": "cmux vm pause \ncmux billing checkout --plan pro", - "hoursMessage": "Go の今月の 40 VM 時間を使い切りました。保存済み VM は次の請求期間から再び利用できます。", - "hoursAction": "cmux billing checkout --plan pro", - "shapeMessage": "Go は 2 vCPU、4 GiB RAM、16 GiB ディスクに対応しています。", - "shapeAction": "cmux vm new --size 4g\ncmux billing checkout --plan pro" - }}, + }, "ios": { "metaTitle": "cmux iOS — iPhone と iPad でターミナルを", "metaDescription": "cmux iOS アプリは、Mac のターミナルを自分の Tailscale やローカルネットワーク経由で iPhone・iPad にリアルタイムでミラーリング。どこからでもコーディングエージェントを操作できます。", @@ -326,8 +292,7 @@ "accountGroup": "アカウント", "billing": "請求", "team": "チーム", - "testflight": "iOS TestFlight", - "irohDevices": "IROH デバイス" + "testflight": "iOS TestFlight" }, "home": { "title": "ダッシュボード", @@ -389,7 +354,7 @@ }, "coderouterAccounts": { "title": "アカウント", - "description": "共有アカウントは、このチームの Cloud VM で利用できます。非公開のアカウントは、追加した本人だけが利用できます。", + "description": "このチームの Cloud VM と CLI 上の Claude Code、Codex、OpenCode はこれらのアカウントを経由します。各マシンはプロバイダーごとに 1 つのアカウントに固定されるためプロンプトキャッシュが機能し続けます。レート制限や認証情報の拒否が起きるとそのアカウントはクールダウンに入り、マシンは次のアカウントに移ります。", "accountsCount": "{count} 件のアカウント", "emptyTitle": "まだアカウントがありません", "emptyBody": "アカウントを追加するまで、このチームからのリクエストは失敗します。", @@ -461,10 +426,7 @@ "removeClaudeConfirmBody": "このアカウントに固定されたマシンはチームの他のアカウントに移ります。最後のアカウントの場合、新しいアカウントを追加するまでこのチームからの Claude リクエストは失敗します。", "removeNativeConfirmBody": "このアカウントに固定されたセッションはチームの他のアカウントに移ります。再追加には cr add で再度サインインが必要です。", "removeSharedConfirmBody": "チーム全体からこのアカウントが削除されます。再追加には再度サインインが必要です。", - "cancelAction": "キャンセル", - "shareWithTeam": "チームと共有", - "makePrivate": "非公開にする", - "privateImportHint": "新しいアカウントは非公開です。共有すると、このチームの Cloud VM で利用できます。" + "cancelAction": "キャンセル" }, "accountMenu": { "label": "アカウントメニュー", @@ -509,12 +471,6 @@ "annualPrice": "${monthly}/月(年払い)", "grantedBody": "このアカウントの Pro アクセスは cmux チームによって付与されています。管理するサブスクリプションはありません。" }, - "go": { - "name": "cmux Go", - "activeBody": "Go プランは有効です。次回更新日は {date} です。", - "pendingBody": "Go プランは {date} に終了します。", - "monthlyPrice": "${amount}/月" - }, "team": { "name": "cmux Team", "fallbackName": "チーム", @@ -549,13 +505,6 @@ "free": "無料", "pro": "Pro", "loading": "読み込み中…" - }, - "max": { - "name": "cmux Max", - "activeBody": "Max プランは有効です。次回更新日は {date} です。", - "pendingBody": "Max プランは {date} に終了します。", - "monthlyPrice": "${amount}/月", - "upsell": "Max は月額 $200 で、マシンごとに最大 64 GB RAM を利用できます。月払いのみです。" } }, "testflight": { @@ -601,24 +550,6 @@ "join": "iOS TestFlightに参加", "leave": "TestFlightから退出" } - }, - "iroh": { - "title": "IROH devices", - "description": "Manage devices in the selected Stack team. Access is filtered by team permissions.", - "team": "Team", - "loading": "Loading devices…", - "empty": "No IROH devices are registered for this team.", - "unavailable": "Dashboard authorization is unavailable.", - "mutationError": "The device change could not be completed.", - "revoke": "Revoke access", - "deviceId": "Device", - "revision": "Revision", - "status": "Status", - "active": "Active", - "revoked": "Revoked", - "relaySettings": "Relay settings", - "relaySettingsDescription": "Choose the relay servers used by this team. Enter one URL per line.", - "saveRelaySettings": "Save relay settings" } }, "vault": { @@ -858,22 +789,18 @@ "metaDescriptionNoVault": "cmux はAIコーディングエージェント向けの無料・オープンソースの macOS ターミナルです。Pro では分離された Cloud VM が使えます。Enterprise は SSO とセルフホストに対応します。", "metaDescriptionNoVaultShort": "cmux は無料・オープンソースの macOS ターミナルです。Pro では Cloud VM を利用でき、Enterprise では SSO とセルフホストに対応します。", "title": "料金", - "categories": { - "individual": { - "title": "個人向け", - "description": "無料で始めて、必要に応じて Cloud の容量を追加できます。" - }, - "business": { - "title": "チームとビジネス向け", - "description": "組織向けの一元請求、管理、コントロール。" - } - }, "alreadyPaid": "購入済みですか?Founder's Edition を接続する", "perMonth": "/月", + "perMonthBilledYearly": "/月(年払い)", "perUserMonth": "/ユーザー/月", + "perUserMonthBilledYearly": "/ユーザー/月(年払い)", "billingPeriod": "請求期間", "monthly": "月払い", + "annual": "年払い", + "saveAnnual": "{discount}%割引", + "annualComparePrice": "${monthly}/月", "teamMonthlyComparePrice": "${monthly}/ユーザー/月", + "teamAnnualComparePrice": "${monthly}/ユーザー/月", "currentPlan": "現在のプラン", "manageBilling": "請求を管理", "free": { @@ -890,24 +817,13 @@ "Discord と GitHub でのコミュニティサポート" ] }, - "go": { - "name": "Go", - "cta": "Go を入手", - "featuresLead": "集中作業向けの小さな Cloud VM:", - "features": [ - "アクティブな Cloud VM 1 台", - "2 vCPU、4 GiB RAM、16 GiB ディスク", - "実行中の VM を含めて合計 2 台の VM を保存", - "毎月 40 VM 時間を含み、上限で VM は一時停止します" - ] - }, "pro": { "name": "Pro", "cta": "Pro を入手", "featuresLead": "Free のすべてに加えて:", "features": [ "分離されたリモートサンドボックスの Cloud VM 上で動くクラウドエージェント", - "最大 50 台の Cloud VM で、24 GB RAM と 6 vCPUを共有", + "最大 50 台の Cloud VM で、24 GB RAM と 6 vCPU を共有", "ワークスペースは無制限", "cmux iOS アプリとメールサポート" ], @@ -919,15 +835,6 @@ "iOS アプリから Mac に接続するホスト型ネットワーク、Tailscale の設定不要" ] }, - "max": { - "name": "Max", - "cta": "Max を入手", - "featuresLead": "Pro のすべてに加えて:", - "features": [ - "マシンごとに最大 64 GB RAM", - "Pro のすべて: 50 台の Cloud VM、無制限のワークスペース、iOS アプリ" - ] - }, "team": { "name": "Team", "cta": "Teams を入手", @@ -958,176 +865,131 @@ "label": "ネイティブ macOS ターミナル、オープンソース", "free": "true", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "自分のキーで使うローカル CLI エージェント", "free": "true", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "縦型タブ、分割、通知、ソケット API", "free": "true", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "セッション履歴", "free": "ローカル", "pro": "無制限", - "max": "無制限", "team": "無制限", "enterprise": "無制限", - "vault": true, - "go": "ローカル" + "vault": true }, { "label": "cmux Vault: クラウドセッションバックアップ", "free": "false", "pro": "true", - "max": "true", "team": "true", "enterprise": "true", - "vault": true, - "go": "false" + "vault": true }, { "label": "すべてのセッションを横断検索", "free": "false", "pro": "true", - "max": "true", "team": "true", "enterprise": "true", - "vault": true, - "go": "false" + "vault": true }, { "label": "Cloud VM 上のクラウドエージェント", "free": "false", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "true" + "enterprise": "true" }, { "label": "同時 Cloud VM 数", "free": "false", "pro": "50", - "max": "50", "team": "ユーザーごとに 50", - "enterprise": "カスタム", - "go": "1" + "enterprise": "カスタム" }, { "label": "すべての Cloud VM で共有するリソース", "free": "false", - "pro": "24 GB RAM と 6 vCPUをすべての VM で共有", - "max": "各 VM に 64 GB RAM と 16 vCPU", - "team": "ユーザーあたり 24 GB RAM と 6 vCPUを、そのユーザーの VM で共有", - "enterprise": "カスタム", - "go": "2 vCPU、4 GiB RAM、16 GiB ディスク" - }, - { - "label": "最大の Cloud VM", - "free": "24 GB RAM", - "pro": "24 GB RAM", - "max": "64 GB RAM", - "team": "24 GB RAM", - "enterprise": "カスタム", - "go": "4 GiB RAM" + "pro": "24 GB RAM と 6 vCPU をすべての VM で共有", + "team": "ユーザーあたり 24 GB RAM と 6 vCPU を、そのユーザーのすべての VM で共有", + "enterprise": "カスタム" }, { "label": "ワークスペース", "free": "無制限", "pro": "無制限", - "max": "無制限", "team": "無制限", - "enterprise": "無制限", - "go": "無制限" + "enterprise": "無制限" }, { "label": "iOS アプリ", "free": "false", "pro": "true", - "max": "true", "team": "true", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "Mac に接続するネットワーク", "free": "持ち込み", "pro": "ホスト型", - "max": "ホスト型", "team": "ホスト型", "enterprise": "セルフホスト", - "hostedNetworking": true, - "go": "持ち込み" + "hostedNetworking": true }, { "label": "SSO と SAML サインイン", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "セルフホストとエアギャップ実行", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "監査ログ: セッション、ネットワーク、ブラウザ", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "ボリューム価格", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "SOC 2 と SLA", "free": "false", "pro": "false", - "max": "false", "team": "false", - "enterprise": "true", - "go": "false" + "enterprise": "true" }, { "label": "サポート", "free": "コミュニティ", "pro": "メール", - "max": "メール", "team": "優先", - "enterprise": "専任", - "go": "コミュニティ" + "enterprise": "専任" } ] }, @@ -1141,11 +1003,7 @@ }, { "q": "Cloud VM とは何ですか?", - "a": "クラウド上の永続的な Linux マシンで、使い捨て(cattle)ではなくペットです。Zen of cmux のとおり、マシンはソリューションではなくプリミティブです。ファイル、インストールしたツール、状態はそのまま保持され、その上に構築したものはあなたのものです。エージェントはそこでリポジトリをクローンし、コマンドを実行し、プルリクエストを開くため、お使いの Mac には触れません。Pro では最大 50 台の Cloud VM で、24 GB RAM と 6 vCPUを共有します。Team ではユーザーごとに同じプールが適用されます。" - }, - { - "q": "Max では何が追加されますか?", - "a": "Max は月払いで $200/月です。Pro のすべてを含み、32 GB または 64 GB RAM の Cloud VM を利用できます。Pro、Team、Free のマシンは最大 24 GB RAM です。" + "a": "クラウド上の永続的な Linux マシンで、使い捨て(cattle)ではなくペットです。Zen of cmux のとおり、マシンはソリューションではなくプリミティブです。ファイル、インストールしたツール、状態はそのまま保持され、その上に構築したものはあなたのものです。エージェントはそこでリポジトリをクローンし、コマンドを実行し、プルリクエストを開くため、お使いの Mac には触れません。Pro では最大 50 台の Cloud VM で、24 GB RAM と 6 vCPU を共有します。Team ではユーザーごとに同じ上限が適用されます。従量課金や超過料金はありません。" }, { "q": "cmux Vault とは何ですか?", @@ -1158,7 +1016,7 @@ }, { "q": "課金はどのように行われますか?", - "a": "有料プランは月払いです。Pro は月額 $50、Max は月額 $200、Team はユーザーあたり月額 $60 です。ターミナルとローカルエージェントは無料のままです。" + "a": "Pro は月払いで $50/月、年払いでは $40/月(20%割引)です。Team は月払いで $60/ユーザー/月、年払いでは $48/ユーザー/月です。有料プランではユーザーごとに最大 50 台の Cloud VM で、ユーザーあたり 24 GB RAM と 6 vCPU を共有します。従量課金や超過料金はありません。ターミナルとローカルエージェントは無料のままです。" }, { "q": "クラウドをセルフホストできますか?", @@ -1166,7 +1024,7 @@ }, { "q": "チームプランはありますか?", - "a": "はい。Team はユーザーあたり月額 $60 で、チーム全体の一元請求と優先サポートが含まれます。" + "a": "はい。Team は月払いで $60/ユーザー/月、年払いでは $48/ユーザー/月で、チーム全体の一元請求と優先サポートが含まれます。" } ] }, @@ -1177,20 +1035,11 @@ "billingError": "チェックアウトの開始中に問題が発生しました。しばらくしてからもう一度お試しください。", "billingUnavailable": "現在、決済をご利用いただけません。時間をおいてお試しください。", "billingCancelled": "チェックアウトはキャンセルされました。請求は発生していません。", - "billingAnnualUnavailable": "新規サブスクリプションでは年払いをご利用いただけません。以下から月払いのプランをお選びください。", - "billingPlanUnavailable": "このプランはまだご利用いただけません。以下から Pro または Max をお選びください。", "billingInvalidPlan": "指定されたプランはご利用いただけません。以下からプランをお選びください。", "signedOutNotice": "サインインしていません。サインインすると、現在のプランの確認や購入済みプランの復元ができます。", "signedOutSignIn": "サインイン", "billingInvalidRelay": "アプリのチェックアウトリンクの有効期限が切れているか、検証できませんでした。cmuxに戻ってもう一度お試しください。", - "welcomeTeam": "cmux Team のご購入が完了しました。", - "audience": { - "label": "料金の対象", - "individual": "個人", - "team": "チームとエンタープライズ" - }, - "priceMonthlyNote": "月払いは${amount}/月", - "priceMonthlyPerUserNote": "月払いは${amount}/ユーザー/月" + "welcomeTeam": "cmux Team のご購入が完了しました。" }, "home": { "taglinePrefix": "次のために作られたターミナル:", @@ -1927,60 +1776,6 @@ } }, "docs": { - "computerUse": { - "title": "cmux Computer Use", - "metaTitle": "cmux Computer Use: AIエージェントでMacを自動操作", - "metaDescription": "cmux Computer UseでClaude CodeとCodexがMacを操作。スクリーンショット、アクセシビリティ、クリック、文字入力でネイティブアプリを自動化します。", - "docsMetaTitle": "Computer Useの設定とエージェントツール", - "docsMetaDescription": "macOSでcmux Computer Useを設定する方法。権限、Claude CodeとCodexのツール、対象アプリ、セッション操作、スキル、問題の解決方法を解説します。", - "tagline": "Claude CodeとCodexにMacを操作させよう。", - "intro": "AIコーディングエージェントがMacのネイティブアプリを操作できます。cmux Computer Useで画面の構造を読み取り、スクリーンショットを撮影し、クリックや文字入力を行います。", - "imageAlt": "アクセシビリティとスクリーンショットの権限を設定するcmux Computer Useの画面", - "caption": "Computer Useの権限は独立したヘルパーが管理するため、初期設定中もターミナルセッションを開いたままにできます。", - "featuresTitle": "Macのネイティブアプリを操作", - "featureInspect": "アクセシビリティツリーとスクリーンショットでアプリの画面を確認。", - "featureAct": "macOSアプリでクリック、文字入力、スクロール、ドラッグ、キーボードショートカットを実行。", - "featureReview": "エージェントのカーソルを見ながら結果を確認し、呼び出し元のターミナルに戻れます。", - "agentsTitle": "cmuxで使うコーディングエージェント", - "agentsBody": "{agents}などのコマンドラインエージェントをcmuxで実行できます。", - "supportTitle": "対応エージェント", - "supportBody": "ネイティブのComputer Useツールは、cmux内で起動したClaude CodeとCodexのセッションに自動接続されます。他のエージェントもターミナルで動作しますが、Mac操作の自動連携にはまだ対応していません。", - "setupTitle": "使い始める", - "step1": "Mac版cmuxをインストールして開きます。「設定」→「Computer Use」でComputer Useを有効にします。", - "step2": "権限ボタンからシステム設定を開き、cmux Computer Useにアクセシビリティと画面収録を許可します。一覧にヘルパーがない場合は、アプリのタイルを権限画面に追加またはドラッグしてください。", - "step3": "cmux内でClaude CodeまたはCodexの新しいセッションを開始します。Computer Useの利用を依頼し、アプリ名と作業を指定してください。ツールは自動接続され、MCPの手動設定は不要です。", - "permissionsTitle": "権限と初期設定", - "permissionsBody": "アクセシビリティはアプリ画面の読み取りと操作に、画面収録はスクリーンショットに必要です。両方をcmux Computer Useに許可してください。cmuxやエージェントを起動するだけでは権限は付与されず、設定画面も自動では開きません。", - "tahoeBody": "macOS Tahoeでは、設定中に表示される追加の画面キャプチャ確認も許可してください。cmuxで両方の権限が許可済みになってから作業を再試行します。", - "promptTitle": "計算機で試す", - "prompt": "cmux Computer Useで計算機を開き、100 + 105をボタンで入力して、結果を見せてください。", - "workflowBody": "最初に対象を確認し、そのスナップショットのコントロールを操作してから、再度確認して結果を検証します。要素インデックスは現在のスナップショット専用です。画面遷移やレイアウト変更後は取得し直してください。クリックの成功通知だけでは作業完了を確認できません。", - "toolsTitle": "エージェントツール", - "toolsBody": "cmux-cuaは同梱のMCPサーバーで、ネイティブアプリ操作用の構造化ツールをエージェントに提供します。以下はシェルコマンドではなくツール名です。引数はエージェントのプロファイルによって異なるため、セッションに接続されたスキーマを参照してください。", - "codexBody": "Codexはアプリ名またはバンドルIDを指定してget_app_stateを呼び、スクリーンショットとアクセシビリティツリーを取得します。現在のelement_indexまたは画像上の座標で操作し、get_app_stateで再確認します。ツール一覧:", - "claudeBody": "Claude Codeはlist_appsとlist_windowsで対象を探し、pidとwindow_idを指定してget_window_stateを呼びます。要素トークンまたは最新のインデックスを優先してください。ネイティブプロファイルでは、後続の対象を無効にしない操作をperform_actionsでまとめて実行できます。", - "controlTitle": "操作を追跡・停止する", - "controlBody": "メニューバーのComputer Use項目に操作中のエージェントが表示されます。「Focus Computer Use」は操作対象アプリを表示し、「Focus Calling Terminal」は呼び出し元に戻ります。ターミナルに戻っても自動操作は止まりません。停止するにはメニューの停止項目を使います。", - "disableBody": "無効にするには設定でComputer Useをオフにします。新しいエージェントからツールを除外する場合は、起動前にCMUX_COMPUTER_USE_MCP_DISABLED=1を設定してください。", - "skillTitle": "スキル", - "skillBody": "cmux-cuaスキルは画面確認と操作の手順を説明します。利用は任意で、macOSの権限は付与しません。永続的に導入する場合はエージェントのスキルインストーラーを使ってください。cmuxは既定ではグローバルスキルをインストールしません。", - "skillLink": "cmux-cuaスキルを読む", - "browserTitle": "ネイティブアプリとブラウザ自動化", - "browserBody": "Computer UseはスクリーンショットとアクセシビリティでMacのウィンドウを操作します。cmuxのブラウザペインにはcmux browserコマンドを使い、DOMスナップショット、セレクター、フォーム、JavaScriptを操作します。ブラウザのサーフェス参照とネイティブアプリの要素インデックスは別のものです。", - "browserLink": "ブラウザ自動化リファレンス", - "troubleTitle": "トラブルシューティング", - "missingTitle": "Computer Useツールが見つからない", - "missingBody": "cmuxの設定でComputer Useを有効にし、cmux内でClaude CodeまたはCodexの新しいセッションを開始します。既存セッションには古いツール設定が残る場合があります。環境変数や管理ポリシーで無効になっていないか確認してください。", - "blockedTitle": "設定が未完了、クリックが失敗、または画像が空白", - "blockedBody": "「設定」→「Computer Use」を開き直し、追加のキャプチャ確認を含めて両方の権限設定を完了します。完了後に再試行してください。ヘルパーの再起動が必要でもターミナルセッションは開いたままにできます。", - "targetTitle": "別のウィンドウが選ばれる、またはクリックがブロックされる", - "targetBody": "アプリを再確認し、現在の状態から要素を選び直します。他のウィンドウに隠れている場合は対象を表示してから再確認してください。古い座標や要素インデックスを再利用しないでください。", - "productLink": "cmux Computer Useの概要", - "copyTitle": "Computer Useのプロンプトをコピー", - "copyPrompt": "プロンプトをコピー", - "copiedPrompt": "コピーしました", - "installPrompt": "この作業では $cmux-cua スキルを使ってください。利用できなければ通常のスキルインストーラーで導入してください。権限がなければcmuxの設定 → Computer Useを開くよう伝えてください。操作前に確認し、変更のたびに検証してください。\n\nタスク:[ここにタスクを記入]" - }, "managedPolicies": { "title": "管理デバイスポリシー(MDM)", "metaTitle": "管理デバイスポリシー(MDM)", @@ -2363,11 +2158,10 @@ "warnBeforeClosingTabXButton": "タブの閉じるボタンでタブを閉じる前に確認を表示します。", "hideTabCloseButton": "ペインのタブバーでタブの閉じるボタンを非表示にします。", "forkConversationDefaultDestination": "タブのコンテキストメニューにある主要な「会話をフォーク」操作のデフォルトのフォーク先です。サブメニューには引き続きすべてのフォーク先が表示されます。", - "globalFontMagnification": "cmux が所有するターミナル、タブタイトル、サイドバー、設定、オーバーレイ、アプリの chrome をこの割合で拡大縮小します。レンダリングされたブラウザページ内容は対象外です。", - "paneResizeStepPixels": "ペインのリサイズショートカットを1回押すごとに移動するピクセル数。範囲は 1~200、既定値は 20 です。変更はすぐに適用されます。" + "globalFontMagnification": "cmux が所有するターミナル、タブタイトル、サイドバー、設定、オーバーレイ、アプリの chrome をこの割合で拡大縮小します。レンダリングされたブラウザページ内容は対象外です。" }, "terminal": { - "adaptiveDefaultTheme": "true(既定)の場合、Ghostty のテーマやターミナルの色が設定されていなければ、外観に追従する既定のパレットを適用します。フォントなどの設定は保持されます。false の場合は Ghostty の固定された組み込みパレットを使用します。theme = light:X,dark:Y を含め、明示的なテーマと色の設定が常に優先されます。", + "adaptiveDefaultTheme": "true(既定)の場合、Ghostty 設定にディレクティブが一つもないときに限り、cmux の管理するライト/ダークパレットを適用します。false の場合、未設定の Ghostty では固定された組み込みパレットを使用します。Ghostty のディレクティブが一つでもあれば管理パレットは適用せず、theme = light:X,dark:Y は引き続き外観に追従します。", "agentHibernation": "通常のエージェント休止の設定です。cmux はアイドル中のバックグラウンドエージェントプロセスを終了して RAM と CPU を解放し、そのタブを開いたときに保存済みセッションから再開します。通常の休止には、再開可能なコーディングエージェントであること、ライフサイクルがアイドルと報告していること、ターミナルが画面外にあること、ライブターミナル数が設定上限を超えていること、アイドル時間と確認時間を通して出力が変化していないことが必要です。これとは別に、メモリ負荷が危険な状態では enabled が false でも、安全なアイドル中のバックグラウンドエージェントを cmux が上限付きのバッチで休止する場合があります。表示中、実行中、入力待ち、最近変更された、またはトランスクリプトを保護できないエージェントは対象外です。プレースホルダーの「再開」ボタンは手動の代替手段です。", "agentHibernationEnabled": "ライブターミナル数の上限に基づく通常のエージェント休止を有効にします。false の場合も、メモリ負荷が危険な状態での安全休止は有効です。", "copyOnSelect": "true の場合、選択が確定したときにターミナルで選択したテキストをシステムクリップボードへコピーします。false の場合、cmux は Ghostty の copy-on-select 上書きを出力せず、選択クリップボードの動作は Ghostty の設定と既定値に従います。", @@ -2509,7 +2303,7 @@ "plusButtonScreenshotAlt": "Worktree Agents、New Terminal、New Browser を表示している cmux プラスボタンの右クリックメニュー", "plusButtonScreenshotCaption": "右クリックメニューは ui.newWorkspace.contextMenu の順序に従います。", "newWorkspaceButton": "プラスボタンのカスタムアクション", - "newWorkspaceButtonDesc": "ui.newWorkspace.action でプラスボタンの動作を上書きします。ui.newWorkspace.contextMenu (または別名 rightClick) で、右クリックメニューの順序を定義します。メニュー項目には、アクション ID、アクションオブジェクト、または { \"type\": \"separator\" } を指定できます。 未設定の場合、メニューには cmux.newWorkspace (⌘N), cmux.newCloudWorkspace (⇧⌘Y), cmux.newCloudMachine (⌘Y), cmux.newTerminal (⌘T), cmux.newBrowser (⇧⌘L) が現在のショートカットとともに表示されます。クラウドの項目は Cloud Machines が有効な場合のみ表示されます。", + "newWorkspaceButtonDesc": "ui.newWorkspace.action でプラスボタンの動作を上書きします。ui.newWorkspace.contextMenu (または別名 rightClick) で、右クリックメニューの順序を定義します。メニュー項目には、アクション ID、アクションオブジェクト、または { \"type\": \"separator\" } を指定できます。", "newWorkspaceWorktreeNote": "この例では、通常のプラスクリックで worktree-agents アクションを実行します。commands のワークスペースコマンドは、見えるセットアップ用ターミナルを使って最初に Git worktree を作成します。Codex と Claude は同時に起動し、ワークスペース固有の状態ファイルを待ってから、作成されたディレクトリへ cd して exec します。", "workspaceActions": "ワークスペースレイアウト", "workspaceActionsDesc": "ワークスペースレイアウトは workspace アクションとして保存され、ワークスペース定義をアクション内に直接埋め込むため、別途 commands エントリは不要です。ワークスペースと同じフィールドに加えて、最初のターミナルにそのターミナル自身のコマンドより先に送信されるブートストラップコマンド setup をサポートします:", @@ -2641,8 +2435,7 @@ "wn-reload": "設定を再読み込み", "wn-quit": "終了" }, - "metaTitle": "キーボードショートカット", - "paneResizeDescription": "Ctrl+Shift+H/J/K/L(左/下/上/右)でフォーカス中のペインをリサイズします。キーを押し続けると連続してリサイズできます。設定 → キーボードショートカットでキー割り当てとペインのリサイズ幅を変更できます。移動幅は cmux.json の app.paneResizeStepPixels でも設定でき、範囲は 1~200、既定値は 20 です。" + "metaTitle": "キーボードショートカット" }, "api": { "title": "CLIリファレンス", @@ -3138,7 +2931,6 @@ "envSocket": "shimが接続するcmuxコントロールソケットのパス" }, "navItems": { - "computerUse": "コンピュータ操作", "gettingStarted": "はじめに", "tui": "cmux TUI", "concepts": "コンセプト", @@ -5641,12 +5433,5 @@ "message": "公開ポートのメールアクセス許可", "action": "emailと、必要に応じてexpiresAtを送信してください。" } - }, - "cliAuthIdentity": { - "email": "メールアドレス", - "emailUnavailable": "メールアドレスを取得できません", - "organization": "組織", - "personalAccount": "個人アカウント", - "switchAccountButton": "別のアカウントを使用" } } diff --git a/web/scripts/build-devbox-freestyle.ts b/web/scripts/build-devbox-freestyle.ts index b98f54fa9f3f..d6dc4a668c01 100644 --- a/web/scripts/build-devbox-freestyle.ts +++ b/web/scripts/build-devbox-freestyle.ts @@ -304,6 +304,12 @@ try { "jq -e '.DefaultSearchProviderSearchURL | test(\"duckduckgo\")' /etc/opt/chrome/policies/managed/cmux.json && echo 'export AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable' > /etc/profile.d/cmux-media.sh", ); + await put("cmux-open-url", "/usr/local/bin/cmux-open-url", 0o755); + await step( + "cloud-browser-opener", + "sh -n /usr/local/bin/cmux-open-url && ln -sfn cmux-open-url /usr/local/bin/xdg-open && ln -sfn cmux-open-url /usr/local/bin/x-www-browser && ln -sfn cmux-open-url /usr/local/bin/sensible-browser", + ); + const cuaVersion = devboxCuaDriverVersion(); await step( "cua-driver", diff --git a/web/scripts/devbox-image-common.ts b/web/scripts/devbox-image-common.ts index 8719f46245cb..683011a899b7 100644 --- a/web/scripts/devbox-image-common.ts +++ b/web/scripts/devbox-image-common.ts @@ -34,12 +34,11 @@ export const devboxDockerfilePath = path.join(devboxDir, "Dockerfile"); export const DEVBOX_TEMPLATE_FILES = [ "Dockerfile", "agent-config.sh", - "cmux-opencode", "chrome-managed-policy.json", "cmux-bashrc", "cmux-devbox-boot", "cmux-motd", - "cmux-prompt.bash", + "cmux-open-url", "cmux-terminfo.sh", "cmux-terminfo.src", "codex-managed.toml", @@ -714,12 +713,6 @@ export function devboxDaemonReadyCondition(): string { ); } -/** Keep snapshot timers tied to the hypervisor, rather than a migrated host TSC. */ -export const devboxSnapshotClockCommand = - "grep -qw kvm-clock /sys/devices/system/clocksource/clocksource0/available_clocksource && " + - "echo kvm-clock > /sys/devices/system/clocksource/clocksource0/current_clocksource && " + - "test \"$(cat /sys/devices/system/clocksource/clocksource0/current_clocksource)\" = kvm-clock"; - /** * Blocks in the guest until {@link devboxDaemonReadyCondition} holds, then * exits 0. Bounded: on timeout it exits 1 with the elapsed budget on stderr, diff --git a/web/scripts/verify-devbox-image.ts b/web/scripts/verify-devbox-image.ts index d68835cd2716..8f6ac7283ef9 100644 --- a/web/scripts/verify-devbox-image.ts +++ b/web/scripts/verify-devbox-image.ts @@ -75,6 +75,7 @@ const FILE_PIN_CHECKS = [ ["agent-config.sh", "/etc/cmux/agent-config.sh"], ["seed-history", "/etc/cmux/seed-history"], ["cmux-devbox-boot", "/usr/local/bin/cmux-devbox-boot"], + ["cmux-open-url", "/usr/local/bin/cmux-open-url"], ["chrome-managed-policy.json", "/etc/opt/chrome/policies/managed/cmux.json"], ].map(([source, target]) => `echo '${shaOf(source)} ${target}' | sha256sum -c -`); @@ -96,6 +97,8 @@ const CHECKS: readonly string[] = [ "google-chrome-stable --version", "jq -e '.DefaultSearchProviderSearchURL | test(\"duckduckgo\")' /etc/opt/chrome/policies/managed/cmux.json >/dev/null && echo chrome-ddg-policy-ok", "grep -q AGENT_BROWSER_EXECUTABLE_PATH /etc/profile.d/cmux-media.sh && echo media-profile-ok", + "test -x /usr/local/bin/cmux-open-url && sh -n /usr/local/bin/cmux-open-url && for opener in xdg-open x-www-browser sensible-browser; do test \"$(readlink /usr/local/bin/$opener)\" = cmux-open-url || exit 1; done && echo cloud-browser-openers-ok", + "bash -lc 'test \"$BROWSER\" = /usr/local/bin/cmux-open-url && test \"$GH_BROWSER\" = /usr/local/bin/cmux-open-url' && echo cloud-browser-env-ok", "cua-driver --version", "ffmpeg -version | head -1 && command -v Xvfb && command -v xdpyinfo && command -v xdotool", // codex's Linux sandbox prerequisite: without the distro bwrap, codex warns diff --git a/web/services/vms/drivers/freestyle.ts b/web/services/vms/drivers/freestyle.ts index ba22625e57c0..143cc80904a3 100644 --- a/web/services/vms/drivers/freestyle.ts +++ b/web/services/vms/drivers/freestyle.ts @@ -11,10 +11,8 @@ import { } from "freestyle"; import { createHash, randomBytes } from "node:crypto"; -import { isIP } from "node:net"; import { Effect } from "effect"; import { announceFreestyleNetwork } from "./freestyleNetworkAnnouncement"; -import { guestResourceReporterInstallCommand } from "../guestResourceReporter"; import { ProviderError, type AttachTransport, @@ -47,10 +45,8 @@ import { devboxDesktopOpenUrl, } from "../images/desktop"; import { recordSpanError, setSpanAttributes, withVmSpan } from "../telemetry"; -import { parseSshPublicKey, scpPrepareCommand, SCP_KEY_TTL_SECONDS } from "./scp"; import { GUEST_CMUX_SHIM, GUEST_CMUX_SHIM_PATH } from "../guestCli"; -import { guestBrowserInstallCommand, guestBrowserReadyCommand } from "../guestBrowser"; -import { guestPromptInstallCommand, type GuestPromptIdentity } from "../guestPrompt"; +import { GUEST_CMUX_OPEN_URL_PATH, GUEST_CMUX_OPEN_URL_SCRIPT } from "../guestBrowserOpen"; import { approveCmuxTuiEnrollment, CMUX_TUI_ATTACH_BUNDLE_NOT_READY_EXIT, @@ -61,10 +57,7 @@ import { cmuxTuiAttachBundleCommand, cmuxTuiDaemonBuild, cmuxTuiDaemonCommand, - cmuxTuiAgentHooksInstallCommand, - cmuxTuiHooksReadyCommand, cmuxTuiInstallCommand, - cmuxTuiPinnedManifestUrl, cmuxTuiLayoutSelector, cmuxTuiPinCheckCommand, cmuxTuiRunCommand, @@ -146,6 +139,7 @@ export const PORT_OPEN_LEASE_TTL_SECONDS = 7 * 24 * 60 * 60; /** Bounds the blocking `systemctl start` of the desktop unit (its own TimeoutStartSec is 120 s). */ const DESKTOP_HEAL_TIMEOUT_MS = 90_000; export const FREESTYLE_ATTACH_TRANSPORT: AttachTransport = "cmux-remote"; + /** * Every guest command the driver runs is administrative — systemd, sudoers, the * daemon install — so it runs as root. The 0.2 API's `linuxUser` default is @@ -199,8 +193,8 @@ export function preconnectFreestyle(): void { /** Exported for the publication provider, which shares this account-wide client. */ export function freestyleClient(timeoutMs = DEFAULT_TIMEOUT_MS): Freestyle { - const longFetch = ((input: URL | RequestInfo, init?: RequestInit) => - fetch(input as Request, { ...(init ?? {}), signal: AbortSignal.timeout(timeoutMs) })) as typeof fetch; + const longFetch: typeof fetch = (input, init) => + fetch(input as Request, { ...(init ?? {}), signal: AbortSignal.timeout(timeoutMs) }); const baseUrl = process.env.FREESTYLE_API_URL?.trim() || undefined; const apiKey = process.env.FREESTYLE_API_KEY?.trim(); if (apiKey) return new Freestyle({ apiKey, baseUrl, fetch: longFetch }); @@ -409,8 +403,8 @@ export function freestyleNetworkAddressMetadata( const ipv4 = network?.ipv4?.trim(); const ipv6 = network?.ipv6?.trim(); return { - ...(ipv4 && isIP(ipv4) === 4 ? { networkIpv4: ipv4 } : {}), - ...(ipv6 && isIP(ipv6) === 6 ? { networkIpv6: ipv6 } : {}), + ...(ipv4 ? { networkIpv4: ipv4 } : {}), + ...(ipv6 ? { networkIpv6: ipv6 } : {}), }; } @@ -893,7 +887,7 @@ export function freestyleSnapshotRef(snapshot: SnapshotData): SnapshotRef { export class FreestyleProvider implements VMProvider { readonly id = "freestyle" as const; - /** The normal terminal transport. SSH is an explicit legacy attach verb, not the default. */ + /** The only session transport: the cmux-tui remote daemon (`openCmuxRemote`). */ readonly attachTransports: readonly AttachTransport[] = ["cmux-remote"]; /** ``create`` honors requested memory through the grow-only size ladder. */ @@ -911,24 +905,6 @@ export class FreestyleProvider implements VMProvider { this.privateNetworking = new FreestylePrivateNetworking(this.deps.client); } - async prepareSCP(vmId: string, publicKey: string): Promise { - return withVmSpan("cmux.vm.provider.prepare_scp", "provider", spanAttributes(vmId, "prepare_scp"), async () => { - const key = parseSshPublicKey(publicKey); - const vm = this.deps.client().vms.ref(vmId); - const data = await vm.data(); - const host = freestylePortAddress(data, vmId); - const expires = new Date(Date.now() + SCP_KEY_TTL_SECONDS * 1000); - const result = await this.execResult(vm, scpPrepareCommand(key, expires)); - if (!result || result.exitCode !== 0) { - throw new ProviderError("freestyle", `SCP preparation failed in ${vmId}: ${(result?.stderr || "SSH server unavailable").slice(0, 500)}`); - } - let hostPublicKey: string; - try { hostPublicKey = parseSshPublicKey(result.stdout); } - catch { throw new ProviderError("freestyle", "SCP preparation returned an invalid guest host key."); } - return { host, port: 22, username: "cmux", hostPublicKey, expiresAtUnix: Math.floor(expires.getTime() / 1000) }; - }); - } - async create(options: CreateOptions): Promise { const image = options.image.trim(); if (!image) { @@ -955,9 +931,6 @@ export class FreestyleProvider implements VMProvider { // Do not let an account/provider idle default turn a persistent // machine into a one-shot box. Explicit pause/stop still works. idleTimeoutSeconds: FREESTYLE_PERSISTENT_IDLE_TIMEOUT_SECONDS, - ...(options.runtimeBudgetSeconds !== undefined ? { - maxRunTotalSeconds: Math.max(0, Math.floor(options.runtimeBudgetSeconds)), automaticRestart: false, - } : {}), metadata: { cmux: "cloud" }, firewall: { rules: freestyleFirewallRules({ publicDaemonIngress: !networkId }) }, ...(networkId ? { vpcs: [{ vpcId: networkId, ipv4: true, ipv6: true }] } : {}), @@ -968,11 +941,6 @@ export class FreestyleProvider implements VMProvider { "cmux.vm.network.private": !!networkId, }); try { - // Validate the provider-assigned VPC address without issuing the - // guest-side announcement exec. The baked supervisor announces on - // clone boot; attach performs the strict announcement before - // handing out the private daemon route. - if (networkId) await this.announcePrivateAddresses(vm, data, { validateOnly: true }); if (options.imageSize) { // One snapshot per size: the machine already boots at the shape // that was sold, so nothing is read back and nothing is grown. @@ -994,13 +962,8 @@ export class FreestyleProvider implements VMProvider { // The in-VM shim is a separate convenience layer over the baked // daemon and is installed idempotently for agents and peer links. - await this.installGuestCli(vm, vmId, options.promptIdentity); - // The baked supervisor announces the VPC interface on clone boot - // and every 30 seconds. Waiting for a second guest-side `ip` probe - // here made create pay a redundant network round trip and turned - // a transient netlink timeout into a destructive rollback. The - // attach path performs the strict announcement/readiness check - // before handing out the private daemon route. + await this.installGuestCli(vm); + await this.announcePrivateAddresses(vm, data); } catch (err) { // A VM that failed to size or configure must not survive as an // orphan, and an undersized machine must not ship as if it were @@ -1114,20 +1077,6 @@ export class FreestyleProvider implements VMProvider { ); } - async setRuntimeBudget(vmId: string, remainingSeconds: number | null): Promise { - const vm = this.deps.client(CREATE_TIMEOUT_MS).vms.ref(vmId); - if (remainingSeconds === null) { - await vm.update({ maxRunTotalSeconds: -1, automaticRestart: true }); - return; - } - const data = await vm.data(); - const used = data.totalRunSeconds; - if (typeof used !== "number" || !Number.isFinite(used) || used < 0 || !Number.isFinite(remainingSeconds) || remainingSeconds < 0) { - throw new ProviderError("freestyle", "setRuntimeBudget", new Error("Provider runtime counter is unavailable")); - } - await vm.update({ maxRunTotalSeconds: Math.floor(used + remainingSeconds), automaticRestart: false }); - } - async resume(vmId: string): Promise { return withVmSpan( "cmux.vm.provider.resume", @@ -1200,7 +1149,9 @@ export class FreestyleProvider implements VMProvider { try { const fs = this.deps.client(timeoutMs + EXEC_OVERHEAD_TIMEOUT_MS); const vm = fs.vms.ref(vmId); - await this.ensureGuestCli(vm, vmId); + const expected = createHash("sha256").update(GUEST_CMUX_SHIM).digest("hex"); + const current = await this.execResult(vm, `test "$(sha256sum '${GUEST_CMUX_SHIM_PATH}' 2>/dev/null | cut -d ' ' -f 1)" = '${expected}'`); + if (current?.exitCode !== 0) await this.installGuestCli(vm); const r = await vm.exec({ command, timeoutMs, linuxUser: GUEST_LINUX_USER }); // statusCode is null when the guest killed the command at its timeout. const exitCode = r.statusCode ?? 124; @@ -1375,7 +1326,7 @@ export class FreestyleProvider implements VMProvider { // remains best-effort for a transient resume race. The new machine's // edge rule is supplied inline, so its route is still fail-closed. try { - await this.installGuestCli(vm, vmId); + await this.installGuestCli(vm); await this.ensureCmuxTuiRunning(vm, vmId, false).catch(() => undefined); await this.announcePrivateAddresses(vm, data); } catch (err) { @@ -1430,27 +1381,16 @@ export class FreestyleProvider implements VMProvider { // an invitation unless the caller is enrolled. Exit 3 means the daemon // was not ready inside the settle budget; heal, then run it again. const fingerprint = options?.deviceFingerprint; - const promptSetup = options?.promptIdentity ? `${guestPromptInstallCommand(options.promptIdentity)} && ` : ""; let bundleResult = await this.execResult( vm, - promptSetup + cmuxTuiAttachBundleCommand({ readyGate: freestyleDaemonSettledCommand(), deviceFingerprint: fingerprint }), + cmuxTuiAttachBundleCommand({ readyGate: freestyleDaemonSettledCommand(), deviceFingerprint: fingerprint }), DAEMON_SETTLE_TIMEOUT_MS + EXEC_OVERHEAD_TIMEOUT_MS + EXEC_DEFAULT_TIMEOUT_MS, ); let healed = false; if (!bundleResult || bundleResult.exitCode === CMUX_TUI_ATTACH_BUNDLE_NOT_READY_EXIT) { healed = true; await this.ensureCmuxTuiRunning(vm, vmId); - bundleResult = await this.execResult(vm, promptSetup + cmuxTuiAttachBundleCommand({ deviceFingerprint: fingerprint })); - } - if (!healed && bundleResult?.exitCode === 0) { - // Healthy existing machines skip daemon healing, but still need - // current OS openers before an interactive terminal is attached. - await this.ensureGuestCli(vm, vmId); - // The healthy fast path skips the heal, so this is where a machine - // that predates hook installation gets its Claude Code and Codex - // hooks (best effort inside). - await this.ensureAgentHooks(vm, vmId); - await this.ensureResourceReporter(vm, vmId); + bundleResult = await this.execResult(vm, cmuxTuiAttachBundleCommand({ deviceFingerprint: fingerprint })); } if (!bundleResult || bundleResult.exitCode !== 0) { throw new ProviderError( @@ -1518,16 +1458,12 @@ export class FreestyleProvider implements VMProvider { ); } - private async announcePrivateAddresses( - vm: Vm, - data: FreestyleRouteAddresses, - options: { readonly validateOnly?: boolean } = {}, - ): Promise { + private async announcePrivateAddresses(vm: Vm, data: FreestyleRouteAddresses): Promise { const addresses = (data.vpcs ?? data.networks ?? []) .flatMap((network) => [network.ipv4, network.ipv6]) .filter((address): address is string => typeof address === "string" && address.trim() !== "") .map((address) => address.trim()); - await Effect.runPromise(announceFreestyleNetwork(vm, addresses, options)); + await Effect.runPromise(announceFreestyleNetwork(vm, addresses)); } @@ -1636,12 +1572,9 @@ export class FreestyleProvider implements VMProvider { */ private async ensureCmuxTuiRunning(vm: Vm, vmId: string, installGuestCli = true): Promise { // Keep the shim present even when the baked daemon is already healthy. - if (installGuestCli) await this.installGuestCli(vm, vmId); + if (installGuestCli) await this.installGuestCli(vm); const healthy = await this.execResult(vm, freestyleDaemonSettledCommand(), DAEMON_SETTLE_TIMEOUT_MS + EXEC_OVERHEAD_TIMEOUT_MS); - if (healthy?.exitCode === 0) { - await this.ensureAgentHooks(vm, vmId); - return; - } + if (healthy?.exitCode === 0) return; const source = await this.deps.resolveDaemonSource("freestyle"); const pinned = await this.execResult(vm, freestylePinCheckCommand(source)); if (pinned?.exitCode !== 0) { @@ -1652,58 +1585,6 @@ export class FreestyleProvider implements VMProvider { } await this.execOrThrow(vm, vmId, freestyleStartDaemonCommand(), 60_000); await waitForCmuxTuiReady(this.cmuxTuiInvoke(vm), "freestyle", vmId); - // A repaired daemon whose binary was still pinned skipped the install - // (and with it the hooks); a resumed machine lands here while its - // supervisor re-keys the daemon. Same idempotent check as the healthy path. - await this.ensureAgentHooks(vm, vmId); - } - - /** - * A healthy daemon from a bake or create that predates hook installation - * has no Claude Code / Codex hooks, so its agents never post turn-completed - * or approval notifications. Install them for the daemon's own commit (the - * pin file the bake wrote, else the live pin the create used), the helper - * beside the binary so the two never disagree in generation. The daemon - * keeps running: it already exports CMUX_TUI_HOOK into every pane, and - * agents read hooks at their next launch. - */ - private async ensureAgentHooks(vm: Vm, vmId: string): Promise { - // Best effort throughout: a hook failure is logged and never costs the - // attach or the heal that called it. - try { - await this.installAgentHooks(vm, vmId); - } catch (err) { - console.warn(`[freestyle] ${vmId}: agent hooks not installed: ${errorMessage(err)}`); - } - } - - private async installAgentHooks(vm: Vm, vmId: string): Promise { - const ready = await this.execResult(vm, cmuxTuiHooksReadyCommand()); - if (ready?.exitCode === 0) return; - const pin = await this.execResult(vm, "cut -d' ' -f2 /etc/cmux/cmux-tui-pin 2>/dev/null"); - const commit = pin?.exitCode === 0 ? pin.stdout.trim() : ""; - // A pinned build published before the helper shipped throws here: the - // daemon is left as it is rather than paired with a helper from another - // generation. - const source = /^[0-9a-f]{40}$/.test(commit) - ? await this.deps.resolveDaemonSource("freestyle", cmuxTuiPinnedManifestUrl(commit)) - : await this.deps.resolveDaemonSource("freestyle"); - await this.execOrThrow(vm, vmId, cmuxTuiAgentHooksInstallCommand(source), CMUX_TUI_INSTALL_TIMEOUT_MS); - } - - /** Advisory telemetry must not prevent the machine or its CLI from working. */ - private async ensureResourceReporter(vm: Vm, vmId: string): Promise { - try { - await this.execOrThrow(vm, vmId, guestResourceReporterInstallCommand(), 5_000); - } catch (error) { - console.warn(`[freestyle] ${vmId}: resource reporter not installed: ${errorMessage(error)}`); - } - } - - private async ensureGuestCli(vm: Vm, vmId: string): Promise { - const expected = createHash("sha256").update(GUEST_CMUX_SHIM).digest("hex"); - const current = await this.execResult(vm, `test "$(sha256sum '${GUEST_CMUX_SHIM_PATH}' 2>/dev/null | cut -d ' ' -f 1)" = '${expected}' && ${guestBrowserReadyCommand}`); - if (current?.exitCode !== 0) await this.installGuestCli(vm, vmId); } /** @@ -1713,13 +1594,23 @@ export class FreestyleProvider implements VMProvider { * the adapter on older images; create/attach callers treat a failed install * as a failed heal. */ - private async installGuestCli(vm: Vm, vmId: string, promptIdentity?: GuestPromptIdentity): Promise { + private async installGuestCli(vm: Vm): Promise { const temporaryPath = `${GUEST_CMUX_SHIM_PATH}.tmp-${randomBytes(12).toString("hex")}`; + const openerTemporaryPath = `${GUEST_CMUX_OPEN_URL_PATH}.tmp-${randomBytes(12).toString("hex")}`; + const openerName = GUEST_CMUX_OPEN_URL_PATH.split("/").pop()!; + const openerEncoded = Buffer.from(GUEST_CMUX_OPEN_URL_SCRIPT, "utf8").toString("base64"); try { await vm.fs.writeTextFile(temporaryPath, GUEST_CMUX_SHIM, { mode: 0o755 }); const result = await vm.exec({ - command: `${guestBrowserInstallCommand()} && chmod 0755 '${temporaryPath}' && mv -f '${temporaryPath}' '${GUEST_CMUX_SHIM_PATH}'` - + (promptIdentity ? ` && ${guestPromptInstallCommand(promptIdentity)}` : ""), + command: [ + `chmod 0755 '${temporaryPath}' && mv -f '${temporaryPath}' '${GUEST_CMUX_SHIM_PATH}'`, + `printf '%s' '${openerEncoded}' | base64 -d > '${openerTemporaryPath}'`, + `chmod 0755 '${openerTemporaryPath}' && mv -f '${openerTemporaryPath}' '${GUEST_CMUX_OPEN_URL_PATH}'`, + `ln -sfn '${openerName}' /usr/local/bin/xdg-open`, + `ln -sfn '${openerName}' /usr/local/bin/x-www-browser`, + `ln -sfn '${openerName}' /usr/local/bin/sensible-browser`, + `printf '%s\\n' 'if [ -z "\${BROWSER-}" ]; then export BROWSER=${GUEST_CMUX_OPEN_URL_PATH}; fi' 'if [ -z "\${GH_BROWSER-}" ]; then export GH_BROWSER=${GUEST_CMUX_OPEN_URL_PATH}; fi' > /etc/profile.d/cmux-browser-open.sh`, + ].join(" && "), timeoutMs: 30_000, linuxUser: GUEST_LINUX_USER, }); @@ -1729,9 +1620,9 @@ export class FreestyleProvider implements VMProvider { } } catch (error) { await vm.fs.remove(temporaryPath).catch(() => undefined); + await vm.fs.remove(openerTemporaryPath).catch(() => undefined); throw error; } - await this.ensureResourceReporter(vm, vmId); } private async execResult(vm: Vm, command: string, timeoutMs = EXEC_DEFAULT_TIMEOUT_MS): Promise { diff --git a/web/services/vms/guestBrowserOpen.ts b/web/services/vms/guestBrowserOpen.ts new file mode 100644 index 000000000000..86340eae27f1 --- /dev/null +++ b/web/services/vms/guestBrowserOpen.ts @@ -0,0 +1,93 @@ +/** Shared shell fragment for terminal-initiated browser opens in a Cloud VM. */ +export const GUEST_CMUX_OPEN_URL_PATH = "/usr/local/bin/cmux-open-url"; + +export const GUEST_CMUX_OPEN_URL_SCRIPT = `#!/bin/sh +# cmux Cloud browser opener. The devbox deliberately owns the common Linux +# browser entry points so terminal tools cannot target the hidden VNC Chrome. +# Chrome, agent-browser, xdotool, and CUA launch their browser explicitly and +# never pass through this shim. +set -eu + +cmux_open_url="\${1:-}" +if [ "\$cmux_open_url" = "--" ]; then cmux_open_url="\${2:-}"; fi +if [ -z "\$cmux_open_url" ]; then + case "\${LC_ALL:-\${LC_MESSAGES:-\${LANG:-en}}}" in + ja*) printf '%s\\n' "この URL を開いてください: (URL がありません)" ;; + *) printf '%s\\n' "Open this URL: (missing URL)" ;; + esac + exit 0 +fi +case "\$cmux_open_url" in + http://*|https://*) ;; + *) + case "\${LC_ALL:-\${LC_MESSAGES:-\${LANG:-en}}}" in + ja*) printf 'この URL を開いてください: %s\\n' "\$cmux_open_url" ;; + *) printf 'Open this URL: %s\\n' "\$cmux_open_url" ;; + esac + exit 0 + ;; +esac + +if cmux_open_cli="\$(command -v cmux 2>/dev/null)" && [ -x "\$cmux_open_cli" ]; then + exec "\$cmux_open_cli" open "\$cmux_open_url" +fi + +# The guest CLI is installed by the driver on attach. Until that happens, keep +# browser-opening callers alive and give the person a usable handoff instead of +# falling through to Chrome on DISPLAY=:1. +case "\${LC_ALL:-\${LC_MESSAGES:-\${LANG:-en}}}" in + ja*) printf 'この URL を開いてください: %s\\n' "\$cmux_open_url" ;; + *) printf 'Open this URL: %s\\n' "\$cmux_open_url" ;; +esac +`; + +export const GUEST_CMUX_OPEN_SHELL = ` +# Ask the Mac to open a URL through the daemon's durable notification stream. +# The caller's terminal and native-mirror attachment are required, so a +# headless exec cannot accidentally create a request in an unrelated workspace. +# A browser-resource fallback keeps older hosts usable when the request verb is +# not available yet. +guest_open_url() { + case "\${1:-}" in + --help|-h|help) cmux_message openHelp; return 0 ;; + esac + [ "\$#" -eq 1 ] && [ -n "\$1" ] || { cmux_message openUsage >&2; return 2; } + cmux_open_url="\$1" + case "\$cmux_open_url" in + http://*|https://*) ;; + *) cmux_message openFallback "\$cmux_open_url"; return 0 ;; + esac + cmux_open_terminal="\${CMUX_TUI_TERMINAL_ID:-}" + if [ -x "\$CMUX_TUI_BIN" ] && [ -n "\$cmux_open_terminal" ]; then + cmux_open_snapshot="\$(tui --json session current snapshot 2>/dev/null || true)" + cmux_open_pane="\$(printf '%s\\n' "\$cmux_open_snapshot" | jq -r --arg term "\$cmux_open_terminal" ' + (.value // .) as \$s + | select(any((\$s.clients // [])[]?; .client_kind == "native-mirror" and any((.attached_terminal_ids // [])[]?; . == \$term))) + | [\$s.tabs[]? | select(.content_kind == "terminal" and .content_id == \$term) | .pane_id] | unique + | select(length == 1) | .[0] // empty + ' 2>/dev/null || true)" + if [ -n "\$cmux_open_pane" ]; then + if cmux_open_result="\$(tui --json notification create --title cmux.open-url --body "\$cmux_open_url" --terminal "\$cmux_open_terminal" 2>/dev/null)"; then + cmux_open_notification_id="\$(printf '%s\\n' "\$cmux_open_result" | jq -r '(.value // .) | .notification_id // .id // empty' 2>/dev/null || true)" + case "\$cmux_open_notification_id" in + notification_*) return 0 ;; + esac + fi + # Older hosts can still materialize a daemon browser resource when a + # provider is present. Keep this compatibility path bounded and verify + # the resource is retained before claiming success. + if cmux_open_result="\$(tui --json pane "\$cmux_open_pane" tab create browser --url "\$cmux_open_url" 2>/dev/null)"; then + cmux_open_browser_id="\$(printf '%s\\n' "\$cmux_open_result" | jq -r '(.value // .) | .browser_id // empty' 2>/dev/null || true)" + if [ -n "\$cmux_open_browser_id" ]; then + cmux_open_snapshot="\$(tui --json session current snapshot 2>/dev/null || true)" + if printf '%s\\n' "\$cmux_open_snapshot" | jq -e --arg id "\$cmux_open_browser_id" 'any(((.value // .).browsers // [])[]; .id == \$id)' >/dev/null 2>&1; then + return 0 + fi + fi + fi + fi + fi + cmux_message openFallback "\$cmux_open_url" + return 0 +} +`; diff --git a/web/services/vms/guestCli.ts b/web/services/vms/guestCli.ts index 083eab0152b8..4de45f9fa1a8 100644 --- a/web/services/vms/guestCli.ts +++ b/web/services/vms/guestCli.ts @@ -31,10 +31,9 @@ // reaches machines created from any existing snapshot. This driver-installed // adapter is the sole source; image bakes keep their promoted CLI until healing. -import { GUEST_CODEROUTER_SHELL } from "./guestCoderouterCli"; import { GUEST_CMUX_MESSAGE_SHELL } from "./guestCliMessages"; import { GUEST_CMUX_TOPOLOGY_SHELL } from "./guestTopologyCli"; -import { GUEST_BROWSER_OPENER_PATH, guestBrowserInstallCommand } from "./guestBrowser"; +import { GUEST_CMUX_OPEN_SHELL } from "./guestBrowserOpen"; export const GUEST_CMUX_SHIM_PATH = "/usr/local/bin/cmux"; @@ -48,11 +47,6 @@ set -eu ${GUEST_CMUX_MESSAGE_SHELL} -if [ "\${1:-}" = open-url ]; then - shift - exec ${GUEST_BROWSER_OPENER_PATH} "$@" -fi - # The daemon binary lives under the daemon's home, which depends on the image # layout (root daemon: /root; layout-aware bakes: the cmux user's home or the # persistent-volume backing path, with /usr/local/bin/cmux-tui symlinked to it). @@ -101,7 +95,7 @@ case "\${1:-}:\${2:-}:\${3:-}" in esac case "\${1:-}:\${2:-}" in workspace:help|workspace:--help|workspace:-h|workspace:|pane:help|pane:--help|pane:-h|pane:|tab:help|tab:--help|tab:-h|tab:|terminal:help|terminal:--help|terminal:-h) ;; - self:*|whoami:*|reflect:*|reflection:*|vm:ls|vm:list|vm:peers|vm:links|vm:help|vm:--help|vm:-h|vm:|:*|help:*|--help:*|-h:*|--version:*|-V:*) ;; + self:*|whoami:*|reflect:*|reflection:*|open:*|open-url:*|vm:ls|vm:list|vm:peers|vm:links|vm:help|vm:--help|vm:-h|vm:|:*|help:*|--help:*|-h:*|--version:*|-V:*) ;; *) [ -x "\$CMUX_TUI_BIN" ] || die_message 1 missingDaemon "\$CMUX_TUI_BIN" ;; esac @@ -839,19 +833,16 @@ guest_agent_command() { esac } -${GUEST_CODEROUTER_SHELL} guest_coderouter_command() { cmux_coderouter_sub="\${1:-help}" [ "\$#" -gt 0 ] && shift case "\$cmux_coderouter_sub" in - accounts|list|ls) guest_coderouter_accounts "\$@" ;; - org|organization|team) guest_coderouter_org "\$@" ;; status|auth) guest_auth_status "\$@" ;; usage|machines) guest_coderouter_usage "\$@" ;; models) guest_coderouter_models "\$@" ;; agent|run) guest_coderouter_agent "\$@" ;; help|--help|-h) guest_usage ;; - claude|login|logout) + claude|accounts|login|logout) die_message 2 accountHostOnly "\$cmux_coderouter_sub" ;; *) die_message 2 unknownCodeRouter "\$cmux_coderouter_sub" ;; @@ -1554,6 +1545,7 @@ default_pane() { } ${GUEST_CMUX_TOPOLOGY_SHELL} +${GUEST_CMUX_OPEN_SHELL} # --------------------------------------------------------------------------- # Layouts as data. \`layout export\` turns a daemon workspace into the same # declarative document the Mac accepts (\`cmux new-workspace --layout\`, cmux.json @@ -2475,6 +2467,10 @@ workspace_verb() { } case "\${1:-}" in + open|open-url) + shift + guest_open_url "\$@" + ;; --version|-v|version) cmux_message version exec "\$CMUX_TUI_BIN" --version @@ -2744,25 +2740,40 @@ case "\${1:-}" in ;; notify) # Mac-CLI compatible \`cmux notify\` inside a machine (agent hooks call it - # with --title/--subtitle/--body). cmux-tui's own \`notify\` verb takes the - # macOS signature: it stores --subtitle as its own field, scopes --clear, - # refuses --reply, and validates any selector it is handed, so the shim - # forwards the arguments verbatim and the daemon stays the one place that - # knows the grammar. Nothing here can name a Mac workspace, surface, or - # socket: those selectors are rejected by the daemon rather than mapped. + # with --title/--subtitle/--body). cmux-tui's verb is \`notification create\`; + # the notification lands in this machine's daemon ledger and the user's Mac + # picks it up from the session event stream it already follows. The Mac + # attributes it to the pane showing this terminal, so the daemon-assigned + # CMUX_TUI_TERMINAL_ID is the only selector that means anything here: Mac + # topology selectors are ignored, --subtitle folds into the body (cmux-tui + # has no such field), and only the levels the daemon accepts are forwarded. + # Nothing here can name a Mac workspace, surface, or socket. shift - # Silent on success like the Mac CLI, unless the caller asked for the - # JSON result: --quiet and --json are exclusive global output modes. - cmux_notify_json= - for cmux_notify_arg in "\$@"; do - case "\$cmux_notify_arg" in - --json|--jsonl) cmux_notify_json=1; break ;; + title=""; subtitle=""; body=""; level=""; terminal="\${CMUX_TUI_TERMINAL_ID:-}" + while [ "\$#" -gt 0 ]; do + case "\$1" in + --title) title="\${2:-}"; shift; [ "\$#" -gt 0 ] && shift ;; + --title=*) title="\${1#--title=}"; shift ;; + --subtitle) subtitle="\${2:-}"; shift; [ "\$#" -gt 0 ] && shift ;; + --subtitle=*) subtitle="\${1#--subtitle=}"; shift ;; + --body) body="\${2:-}"; shift; [ "\$#" -gt 0 ] && shift ;; + --body=*) body="\${1#--body=}"; shift ;; + --level) level="\${2:-}"; shift; [ "\$#" -gt 0 ] && shift ;; + --level=*) level="\${1#--level=}"; shift ;; + --terminal) terminal="\${2:-}"; shift; [ "\$#" -gt 0 ] && shift ;; + --terminal=*) terminal="\${1#--terminal=}"; shift ;; + --workspace|--surface|--window|--tab|--panel) shift; [ "\$#" -gt 0 ] && shift ;; + *) shift ;; esac done - if [ -n "\$cmux_notify_json" ]; then - exec "\$CMUX_TUI_BIN" --session "\$LOCAL_SESSION" notify "\$@" + [ -n "\$title" ] || title=Notification + if [ -n "\$subtitle" ]; then + if [ -n "\$body" ]; then body="\$subtitle — \$body"; else body="\$subtitle"; fi fi - exec "\$CMUX_TUI_BIN" --session "\$LOCAL_SESSION" --quiet notify "\$@" + set -- notification create --title "\$title" --body "\$body" + case "\$level" in info|warning|error) set -- "\$@" --level "\$level" ;; esac + if [ -n "\$terminal" ]; then set -- "\$@" --terminal "\$terminal"; fi + exec "\$CMUX_TUI_BIN" --session "\$LOCAL_SESSION" --quiet "\$@" ;; *) # Local daemon session. cmux-tui's own grammar is \`cmux \`. @@ -2776,7 +2787,6 @@ export function guestCliInstallCommand(): string { const encoded = Buffer.from(GUEST_CMUX_SHIM, "utf8").toString("base64"); return [ `printf '%s' '${encoded}' | base64 -d > ${GUEST_CMUX_SHIM_PATH}.tmp`, - guestBrowserInstallCommand(), `chmod 0755 ${GUEST_CMUX_SHIM_PATH}.tmp`, `mv ${GUEST_CMUX_SHIM_PATH}.tmp ${GUEST_CMUX_SHIM_PATH}`, ].join(" && "); diff --git a/web/services/vms/images/devbox/Dockerfile b/web/services/vms/images/devbox/Dockerfile index 47dad1fb15dd..c22c07fc8416 100644 --- a/web/services/vms/images/devbox/Dockerfile +++ b/web/services/vms/images/devbox/Dockerfile @@ -162,6 +162,15 @@ ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable RUN jq -e '.DefaultSearchProviderSearchURL | test("duckduckgo")' /etc/opt/chrome/policies/managed/cmux.json \ && echo 'export AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable' > /etc/profile.d/cmux-media.sh +# Terminal browser openers route through cmux; explicit Chrome/agent-browser +# launches still use the desktop. The driver installs the guest cmux command. +COPY cmux-open-url /usr/local/bin/cmux-open-url +RUN chmod 0755 /usr/local/bin/cmux-open-url \ + && sh -n /usr/local/bin/cmux-open-url \ + && ln -sfn cmux-open-url /usr/local/bin/xdg-open \ + && ln -sfn cmux-open-url /usr/local/bin/x-www-browser \ + && ln -sfn cmux-open-url /usr/local/bin/sensible-browser + # cua computer-use driver, pinned. It acts on whatever X display a shell # points it at: the VNC desktop below, once /etc/cmux/desktop-env.sh has # exported DISPLAY and the accessibility bus (`cua-driver doctor` proves it). diff --git a/web/services/vms/images/devbox/agent-config.sh b/web/services/vms/images/devbox/agent-config.sh index 1f1b111e37bd..199d1897e4b7 100644 --- a/web/services/vms/images/devbox/agent-config.sh +++ b/web/services/vms/images/devbox/agent-config.sh @@ -29,6 +29,12 @@ if [ -z "${NODE_EXTRA_CA_CERTS-}" ] && [ -f /usr/local/share/ca-certificates/fre export NODE_EXTRA_CA_CERTS fi +# Human browser opens belong to the attached Mac, while explicit Chrome and +# agent-browser commands keep using the desktop on DISPLAY=:1. The common +# Linux opener names are PATH shims to this same command in the devbox image. +if [ -z "${BROWSER-}" ]; then export BROWSER=/usr/local/bin/cmux-open-url; fi +if [ -z "${GH_BROWSER-}" ]; then export GH_BROWSER=/usr/local/bin/cmux-open-url; fi + cmux_write_agent_configs() { # Every model-plane var, persisted generically; OPENAI_BASE_URL is the # anchor: its presence means a boot env is present. diff --git a/web/services/vms/images/devbox/cmux-open-url b/web/services/vms/images/devbox/cmux-open-url new file mode 100755 index 000000000000..9051ee7b0e9a --- /dev/null +++ b/web/services/vms/images/devbox/cmux-open-url @@ -0,0 +1,38 @@ +#!/bin/sh +# cmux Cloud browser opener. The devbox deliberately owns the common Linux +# browser entry points so terminal tools cannot target the hidden VNC Chrome. +# Chrome, agent-browser, xdotool, and CUA launch their browser explicitly and +# never pass through this shim. +set -eu + +cmux_open_url="${1:-}" +if [ "$cmux_open_url" = "--" ]; then cmux_open_url="${2:-}"; fi +if [ -z "$cmux_open_url" ]; then + case "${LC_ALL:-${LC_MESSAGES:-${LANG:-en}}}" in + ja*) printf '%s\n' "この URL を開いてください: (URL がありません)" ;; + *) printf '%s\n' "Open this URL: (missing URL)" ;; + esac + exit 0 +fi +case "$cmux_open_url" in + http://*|https://*) ;; + *) + case "${LC_ALL:-${LC_MESSAGES:-${LANG:-en}}}" in + ja*) printf 'この URL を開いてください: %s\n' "$cmux_open_url" ;; + *) printf 'Open this URL: %s\n' "$cmux_open_url" ;; + esac + exit 0 + ;; +esac + +if cmux_open_cli="$(command -v cmux 2>/dev/null)" && [ -x "$cmux_open_cli" ]; then + exec "$cmux_open_cli" open "$cmux_open_url" +fi + +# The guest CLI is installed by the driver on attach. Until that happens, keep +# browser-opening callers alive and give the person a usable handoff instead of +# falling through to Chrome on DISPLAY=:1. +case "${LC_ALL:-${LC_MESSAGES:-${LANG:-en}}}" in + ja*) printf 'この URL を開いてください: %s\n' "$cmux_open_url" ;; + *) printf 'Open this URL: %s\n' "$cmux_open_url" ;; +esac diff --git a/web/services/vms/images/manifest.json b/web/services/vms/images/manifest.json index 66e811ccd1de..4c843d9bcfbf 100644 --- a/web/services/vms/images/manifest.json +++ b/web/services/vms/images/manifest.json @@ -6993,451 +6993,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-sm", - "imageId": "sh-434afeb0ba0c4e368ca432188ca9501d", + "version": "freestyle-cmux-devbox-20260911-032526-sm", + "imageId": "sh-cd099a44912648399e0420df9b4e7f4f", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "sm", - "cpu": 2, - "memoryMb": 4096, - "storageMb": 16384, - "freestyleBase": "freestyle/ubuntu-sm" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-md", - "imageId": "sh-5d8fa1061b4d41ad819be2a803c99162", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "desktop", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "md", - "cpu": 4, - "memoryMb": 8192, - "storageMb": 32768, - "freestyleBase": "freestyle/ubuntu" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-lg", - "imageId": "sh-cc00c1314d75407486bad1c2db6b7941", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "desktop", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "lg", - "cpu": 8, - "memoryMb": 16384, - "storageMb": 65536, - "freestyleBase": "freestyle/ubuntu-lg" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-lgx", - "imageId": "sh-95d3d37fa8d24686b9b42e2f9147c887", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "desktop", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "lgx", - "cpu": 12, - "memoryMb": 24576, - "storageMb": 98304 - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-xl", - "imageId": "sh-4510d2b5069748c29e9dda0fc7d71b17", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "desktop", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "xl", - "cpu": 16, - "memoryMb": 32768, - "storageMb": 131072, - "freestyleBase": "freestyle/ubuntu-xl" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-2xl", - "imageId": "sh-f3c49f37968649f0abbd5ee42f7b3733", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "desktop", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "2xl", - "cpu": 32, - "memoryMb": 65536, - "storageMb": 131072, - "freestyleBase": "freestyle/ubuntu-2xl" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-sm-base", - "imageId": "sh-434afeb0ba0c4e368ca432188ca9501d", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "base", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "sm", - "cpu": 2, - "memoryMb": 4096, - "storageMb": 16384, - "freestyleBase": "freestyle/ubuntu-sm" - }, - "defaultForLocalDev": false - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-md-base", - "imageId": "sh-5d8fa1061b4d41ad819be2a803c99162", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "base", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "md", - "cpu": 4, - "memoryMb": 8192, - "storageMb": 32768, - "freestyleBase": "freestyle/ubuntu" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-lg-base", - "imageId": "sh-cc00c1314d75407486bad1c2db6b7941", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "base", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "lg", - "cpu": 8, - "memoryMb": 16384, - "storageMb": 65536, - "freestyleBase": "freestyle/ubuntu-lg" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-lgx-base", - "imageId": "sh-95d3d37fa8d24686b9b42e2f9147c887", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "base", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "lgx", - "cpu": 12, - "memoryMb": 24576, - "storageMb": 98304 - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-xl-base", - "imageId": "sh-4510d2b5069748c29e9dda0fc7d71b17", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "base", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "xl", - "cpu": 16, - "memoryMb": 32768, - "storageMb": 131072, - "freestyleBase": "freestyle/ubuntu-xl" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks-2xl-base", - "imageId": "sh-f3c49f37968649f0abbd5ee42f7b3733", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "base", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "213e875f4a7664dbe5bca03cc60bf1e4ac1f675a", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "798f6ba9d4bfafb40d55c974c3aaa02d7bca04e79a998a9a585e5d82b82ed169", - "schema": 2 - }, - "builtAt": "2026-09-11T03:41:45.008Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", - "agentToolResolvedVersions": { - "@anthropic-ai/claude-code": "2.1.267", - "@openai/codex": "0.154.0", - "opencode-ai": "1.18.30", - "@earendil-works/pi-coding-agent": "0.85.1", - "agent-browser": "0.37.1" - }, - "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-434afeb0ba0c4e368ca432188ca9501d, md=sh-5d8fa1061b4d41ad819be2a803c99162, lg=sh-cc00c1314d75407486bad1c2db6b7941, xl=sh-4510d2b5069748c29e9dda0fc7d71b17, lgx=sh-95d3d37fa8d24686b9b42e2f9147c887, 2xl=sh-f3c49f37968649f0abbd5ee42f7b3733.", - "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", - "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", - "defaultForKind": false, - "size": { - "name": "2xl", - "cpu": 32, - "memoryMb": 65536, - "storageMb": 131072, - "freestyleBase": "freestyle/ubuntu-2xl" - } - }, - { - "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-sm", - "imageId": "sh-d65ecd5fd6614ded9483a70d5e68ed28", - "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", - "kind": "desktop", - "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", - "epoch": "2026-09-10-r2", - "devboxSource": { - "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7446,7 +7015,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7460,20 +7029,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-md", - "imageId": "sh-1a67fb62b8bb456ca56164819941f450", + "version": "freestyle-cmux-devbox-20260911-032526-md", + "imageId": "sh-f5be56c7d50048278a66f1b654153bd3", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7482,7 +7051,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7496,20 +7065,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-lg", - "imageId": "sh-dfda28a462c943cca0a8a58153dabd58", + "version": "freestyle-cmux-devbox-20260911-032526-lg", + "imageId": "sh-50d26e0ace92459f9741708b1a312e0a", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7518,7 +7087,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7532,20 +7101,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-lgx", - "imageId": "sh-defeb7e51dd3455caecbafc79c13d30f", + "version": "freestyle-cmux-devbox-20260911-032526-lgx", + "imageId": "sh-6835b528b6a949e38c8fc0c2bc8164ba", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7554,7 +7123,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7567,20 +7136,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-xl", - "imageId": "sh-c6dad4da08e9431b966f0b36c06b01c8", + "version": "freestyle-cmux-devbox-20260911-032526-xl", + "imageId": "sh-882fde940e3440ffaa150a0a266f8d94", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7589,7 +7158,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7603,20 +7172,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-2xl", - "imageId": "sh-679228fc42ea499095004e2004864bdb", + "version": "freestyle-cmux-devbox-20260911-032526-2xl", + "imageId": "sh-5896573bcc4e4d54b7b72f63737632be", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7625,7 +7194,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7639,20 +7208,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-sm-base", - "imageId": "sh-d65ecd5fd6614ded9483a70d5e68ed28", + "version": "freestyle-cmux-devbox-20260911-032526-sm-base", + "imageId": "sh-cd099a44912648399e0420df9b4e7f4f", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7661,7 +7230,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7676,20 +7245,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-md-base", - "imageId": "sh-1a67fb62b8bb456ca56164819941f450", + "version": "freestyle-cmux-devbox-20260911-032526-md-base", + "imageId": "sh-f5be56c7d50048278a66f1b654153bd3", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7698,7 +7267,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7712,20 +7281,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-lg-base", - "imageId": "sh-dfda28a462c943cca0a8a58153dabd58", + "version": "freestyle-cmux-devbox-20260911-032526-lg-base", + "imageId": "sh-50d26e0ace92459f9741708b1a312e0a", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7734,7 +7303,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7748,20 +7317,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-lgx-base", - "imageId": "sh-defeb7e51dd3455caecbafc79c13d30f", + "version": "freestyle-cmux-devbox-20260911-032526-lgx-base", + "imageId": "sh-6835b528b6a949e38c8fc0c2bc8164ba", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7770,7 +7339,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7783,20 +7352,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-xl-base", - "imageId": "sh-c6dad4da08e9431b966f0b36c06b01c8", + "version": "freestyle-cmux-devbox-20260911-032526-xl-base", + "imageId": "sh-882fde940e3440ffaa150a0a266f8d94", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7805,7 +7374,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -7819,20 +7388,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-hooks2-2xl-base", - "imageId": "sh-679228fc42ea499095004e2004864bdb", + "version": "freestyle-cmux-devbox-20260911-032526-2xl-base", + "imageId": "sh-5896573bcc4e4d54b7b72f63737632be", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "d5a15cc5dad82abbdafadcf334498750b9e47736", + "repoCommit": "3774a642b2d4d96e42de5ee965726fc93ee05db9", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "30efe8c3c44afe55f5729756997d2f32500f6031d91cba4eff223633f37b8f7f", + "digest": "789b829baf9e61b69f74d6d33b008f989cf5ea26ff0ac0795a8a96cbe22823db", "schema": 2 }, - "builtAt": "2026-09-11T04:27:41.357Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T03:30:51.644Z", + "builderScriptVersion": "1fc0c4bac3dae2dd6c1b4ac3037568cd693ec13b37f12d4c459e6ad14956a5aa", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -7841,7 +7410,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-d65ecd5fd6614ded9483a70d5e68ed28, md=sh-1a67fb62b8bb456ca56164819941f450, lg=sh-dfda28a462c943cca0a8a58153dabd58, xl=sh-c6dad4da08e9431b966f0b36c06b01c8, lgx=sh-defeb7e51dd3455caecbafc79c13d30f, 2xl=sh-679228fc42ea499095004e2004864bdb.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-cd099a44912648399e0420df9b4e7f4f, md=sh-f5be56c7d50048278a66f1b654153bd3, lg=sh-50d26e0ace92459f9741708b1a312e0a, lgx=sh-6835b528b6a949e38c8fc0c2bc8164ba, xl=sh-882fde940e3440ffaa150a0a266f8d94, 2xl=sh-5896573bcc4e4d54b7b72f63737632be.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8286,20 +7855,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-sm", - "imageId": "sh-307f0a6dea1240dc94ccf22ba5adcc60", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-sm", + "imageId": "sh-115c6b75c97c4b4d95f338d0e019ef94", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8308,7 +7877,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8322,20 +7891,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-md", - "imageId": "sh-b8c25737393b4cee9177923c6c04f894", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-md", + "imageId": "sh-72d0090ddfc44c0e96b81d782e4ca8dc", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8344,7 +7913,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8358,20 +7927,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-lg", - "imageId": "sh-d07f57f6e14144159c4f3df441f5f661", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-lg", + "imageId": "sh-944fe4f4673b41289c24dda3c1310d44", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8380,7 +7949,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8394,20 +7963,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-lgx", - "imageId": "sh-14d5835919154833a4e088ebf0f5c018", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-lgx", + "imageId": "sh-144c594825094165a43e8bbe1cfbc658", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8416,7 +7985,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8429,20 +7998,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-xl", - "imageId": "sh-f43f2dc226514452a891e5ff3e3098b0", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-xl", + "imageId": "sh-801eb61d27ca4ec89fb9bfacf6b6bab3", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8451,7 +8020,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8465,20 +8034,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-2xl", - "imageId": "sh-13efeb95e5894be781ec96c8d25430ac", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-2xl", + "imageId": "sh-0f25d27ac88a4df9b5c0b3f3f0356c03", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8487,7 +8056,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8501,20 +8070,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-sm-base", - "imageId": "sh-307f0a6dea1240dc94ccf22ba5adcc60", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-sm-base", + "imageId": "sh-115c6b75c97c4b4d95f338d0e019ef94", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8523,7 +8092,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8538,20 +8107,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-md-base", - "imageId": "sh-b8c25737393b4cee9177923c6c04f894", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-md-base", + "imageId": "sh-72d0090ddfc44c0e96b81d782e4ca8dc", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8560,7 +8129,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8574,20 +8143,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-lg-base", - "imageId": "sh-d07f57f6e14144159c4f3df441f5f661", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-lg-base", + "imageId": "sh-944fe4f4673b41289c24dda3c1310d44", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8596,7 +8165,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8610,20 +8179,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-lgx-base", - "imageId": "sh-14d5835919154833a4e088ebf0f5c018", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-lgx-base", + "imageId": "sh-144c594825094165a43e8bbe1cfbc658", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8632,7 +8201,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8645,20 +8214,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-xl-base", - "imageId": "sh-f43f2dc226514452a891e5ff3e3098b0", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-xl-base", + "imageId": "sh-801eb61d27ca4ec89fb9bfacf6b6bab3", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8667,7 +8236,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8681,20 +8250,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-12296-hooks4-2xl-base", - "imageId": "sh-13efeb95e5894be781ec96c8d25430ac", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-2xl-base", + "imageId": "sh-0f25d27ac88a4df9b5c0b3f3f0356c03", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "5f6e86c6765227564658c836965ba9a6a458f4f5", + "repoCommit": "0e9aeee04efa0eb0496738c2abc793765c411509", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "e0ba4dbfada681dd22d066e8cea6f885cf960e0342cc280f55d2a5a6c0fea3d4", + "digest": "8c5f7cdf275262e9b6c47596fe63a9759a7a2663e513a2c64fdde63e34e0c81c", "schema": 2 }, - "builtAt": "2026-09-12T05:12:02.621Z", - "builderScriptVersion": "67b11dde0e8da54b80c1d7be017d4b5e6dabd869f4ff46eca2dc718b1ed31cbc", + "builtAt": "2026-09-11T23:46:38.475Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8703,7 +8272,7 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-307f0a6dea1240dc94ccf22ba5adcc60, lg=sh-d07f57f6e14144159c4f3df441f5f661, xl=sh-f43f2dc226514452a891e5ff3e3098b0, lgx=sh-14d5835919154833a4e088ebf0f5c018, md=sh-b8c25737393b4cee9177923c6c04f894, 2xl=sh-13efeb95e5894be781ec96c8d25430ac.", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-11 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-115c6b75c97c4b4d95f338d0e019ef94, md=sh-72d0090ddfc44c0e96b81d782e4ca8dc, lg=sh-944fe4f4673b41289c24dda3c1310d44, lgx=sh-144c594825094165a43e8bbe1cfbc658, xl=sh-801eb61d27ca4ec89fb9bfacf6b6bab3, 2xl=sh-0f25d27ac88a4df9b5c0b3f3f0356c03.", "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, @@ -8717,20 +8286,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-sm", - "imageId": "sh-285fd804575248e9a193561e6a71c07d", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-sm", + "imageId": "sh-406d766217054ccf9501b88dc967db67", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8739,9 +8308,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "sm", @@ -8753,20 +8322,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-md", - "imageId": "sh-a282ea94065f4e3f89e8b63ad5229292", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-md", + "imageId": "sh-561186627bf74a9aafcbb65ef1d6b6ae", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8775,9 +8344,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "md", @@ -8789,20 +8358,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-lg", - "imageId": "sh-23dde0a0a7cc4482970ea091df5e926b", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-lg", + "imageId": "sh-e570a4d85fef4158b249d88a36b297a0", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8811,9 +8380,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lg", @@ -8825,20 +8394,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-lgx", - "imageId": "sh-2d0a19e477e0419bbab5b4447a8cead2", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-lgx", + "imageId": "sh-1422fb6f6f0741e095ae289a57802343", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8847,9 +8416,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lgx", @@ -8860,20 +8429,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-xl", - "imageId": "sh-302163dff19c439499ba6e78d6d34ed5", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-xl", + "imageId": "sh-8083b92fc55f4e2ca4106e7c213c1f62", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8882,9 +8451,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "xl", @@ -8896,20 +8465,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-2xl", - "imageId": "sh-324baafd44ad4010abac11bbc882123f", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-2xl", + "imageId": "sh-f389fbe987a445139011562c8f09f241", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8918,9 +8487,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "2xl", @@ -8932,20 +8501,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-sm-base", - "imageId": "sh-285fd804575248e9a193561e6a71c07d", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-sm-base", + "imageId": "sh-406d766217054ccf9501b88dc967db67", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8954,9 +8523,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "sm", @@ -8969,20 +8538,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-md-base", - "imageId": "sh-a282ea94065f4e3f89e8b63ad5229292", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-md-base", + "imageId": "sh-561186627bf74a9aafcbb65ef1d6b6ae", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -8991,9 +8560,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "md", @@ -9005,20 +8574,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-lg-base", - "imageId": "sh-23dde0a0a7cc4482970ea091df5e926b", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-lg-base", + "imageId": "sh-e570a4d85fef4158b249d88a36b297a0", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9027,9 +8596,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lg", @@ -9041,20 +8610,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-lgx-base", - "imageId": "sh-2d0a19e477e0419bbab5b4447a8cead2", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-lgx-base", + "imageId": "sh-1422fb6f6f0741e095ae289a57802343", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9063,9 +8632,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lgx", @@ -9076,20 +8645,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-xl-base", - "imageId": "sh-302163dff19c439499ba6e78d6d34ed5", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-xl-base", + "imageId": "sh-8083b92fc55f4e2ca4106e7c213c1f62", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9098,9 +8667,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "xl", @@ -9112,20 +8681,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-20260913-120438-2xl-base", - "imageId": "sh-324baafd44ad4010abac11bbc882123f", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r2-2xl-base", + "imageId": "sh-f389fbe987a445139011562c8f09f241", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "a741e72a04b6a8259cf2509fe4fbba81b2bbdd40", + "repoCommit": "2695a4b91a66b65f53e5eca3b73c52742264901b", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "fb6123e9b3e24fc6f9d6b71e1300e0a2114cdeaa586e823a1ea6152396da05f7", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-13T12:09:07.262Z", - "builderScriptVersion": "7505e241a75d5a0808886f963fc84e814823963d0ecc46e4b533c26c7a9478e4", + "builtAt": "2026-09-12T01:26:23.897Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9134,9 +8703,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 01ab9d104d, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-13 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-285fd804575248e9a193561e6a71c07d, md=sh-a282ea94065f4e3f89e8b63ad5229292, lg=sh-23dde0a0a7cc4482970ea091df5e926b, lgx=sh-2d0a19e477e0419bbab5b4447a8cead2, xl=sh-302163dff19c439499ba6e78d6d34ed5, 2xl=sh-324baafd44ad4010abac11bbc882123f.", - "cmuxTuiCommit": "01ab9d104d263f9afd99d43bacbbc9adc1dc4898", - "cmuxTuiSha256": "bfd7894110f7a5d2b5031b2babeaf1b26950e87b54bfbc52de459e3d7a60116f", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-406d766217054ccf9501b88dc967db67, lg=sh-e570a4d85fef4158b249d88a36b297a0, md=sh-561186627bf74a9aafcbb65ef1d6b6ae, xl=sh-8083b92fc55f4e2ca4106e7c213c1f62, lgx=sh-1422fb6f6f0741e095ae289a57802343, 2xl=sh-f389fbe987a445139011562c8f09f241.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "2xl", @@ -9148,20 +8717,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-sm", - "imageId": "sh-1fab582a77924838af8ae8825db329ad", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-sm", + "imageId": "sh-32c50a04a3504fcba8a98c95aad17a93", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9170,9 +8739,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "sm", @@ -9184,20 +8753,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-md", - "imageId": "sh-397d9f1bacb24f77870a845420d14ea7", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-md", + "imageId": "sh-6ce4b246b3304823b21bc7a28cba0603", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9206,9 +8775,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "md", @@ -9220,20 +8789,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-lg", - "imageId": "sh-8ec5705012294c799165f0518d1a4baa", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-lg", + "imageId": "sh-10f00a3c016d4cd0a0aefcda63fd617e", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9242,9 +8811,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lg", @@ -9256,20 +8825,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-lgx", - "imageId": "sh-06f00648f84b44f0b2c42b14ab762628", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-lgx", + "imageId": "sh-fdce651c10364e2d97f143abc750b5c5", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9278,9 +8847,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lgx", @@ -9291,20 +8860,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-xl", - "imageId": "sh-208fd4e275c64a3cad036f214d03e57b", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-xl", + "imageId": "sh-7f6ab8d8ec604bbc8c529e29b33b398f", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9313,9 +8882,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "xl", @@ -9327,20 +8896,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-2xl", - "imageId": "sh-efbaa7c89cfc4d188b17d1827819afc3", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-2xl", + "imageId": "sh-71b15cd621ae46bab7fa41000f5efc4a", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9349,9 +8918,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "2xl", @@ -9363,20 +8932,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-sm-base", - "imageId": "sh-1fab582a77924838af8ae8825db329ad", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-sm-base", + "imageId": "sh-32c50a04a3504fcba8a98c95aad17a93", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9385,9 +8954,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "sm", @@ -9400,20 +8969,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-md-base", - "imageId": "sh-397d9f1bacb24f77870a845420d14ea7", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-md-base", + "imageId": "sh-6ce4b246b3304823b21bc7a28cba0603", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9422,9 +8991,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "md", @@ -9436,20 +9005,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-lg-base", - "imageId": "sh-8ec5705012294c799165f0518d1a4baa", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-lg-base", + "imageId": "sh-10f00a3c016d4cd0a0aefcda63fd617e", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9458,9 +9027,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lg", @@ -9472,20 +9041,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-lgx-base", - "imageId": "sh-06f00648f84b44f0b2c42b14ab762628", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-lgx-base", + "imageId": "sh-fdce651c10364e2d97f143abc750b5c5", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9494,9 +9063,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "lgx", @@ -9507,20 +9076,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-xl-base", - "imageId": "sh-208fd4e275c64a3cad036f214d03e57b", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-xl-base", + "imageId": "sh-7f6ab8d8ec604bbc8c529e29b33b398f", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9529,9 +9098,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "xl", @@ -9543,20 +9112,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-cloud-latency-12739-r4-2xl-base", - "imageId": "sh-efbaa7c89cfc4d188b17d1827819afc3", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r3-2xl-base", + "imageId": "sh-71b15cd621ae46bab7fa41000f5efc4a", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "582f337b1d0b9809e69d18210dc089d261455a3f", + "repoCommit": "0a49e6d5dd766c6ebb93ba890cd6a93b932fa814", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "cfd7c332fb3a10edfb64a7d6d1e1e45ce801c82e32e13ce04b61efc1014828ed", + "digest": "ef51d3ab8be834c4cb02154fb21cfcd13e4f3a6caf8a43a0afb0d6e7b9b1f1a9", "schema": 2 }, - "builtAt": "2026-09-17T01:56:14.318Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:15:40.184Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9565,9 +9134,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 68df8eec35, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-17 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-1fab582a77924838af8ae8825db329ad, md=sh-397d9f1bacb24f77870a845420d14ea7, lg=sh-8ec5705012294c799165f0518d1a4baa, lgx=sh-06f00648f84b44f0b2c42b14ab762628, xl=sh-208fd4e275c64a3cad036f214d03e57b, 2xl=sh-efbaa7c89cfc4d188b17d1827819afc3.", - "cmuxTuiCommit": "68df8eec35f711ea0f6d55b9df7d00a1f0747d35", - "cmuxTuiSha256": "252791a57bddae02d764d924f5bb854ec719add17576c8cde5a11807c8592bc5", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32c50a04a3504fcba8a98c95aad17a93, md=sh-6ce4b246b3304823b21bc7a28cba0603, lg=sh-10f00a3c016d4cd0a0aefcda63fd617e, lgx=sh-fdce651c10364e2d97f143abc750b5c5, xl=sh-7f6ab8d8ec604bbc8c529e29b33b398f, 2xl=sh-71b15cd621ae46bab7fa41000f5efc4a.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": false, "size": { "name": "2xl", @@ -9579,20 +9148,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-sm", - "imageId": "sh-fccff69701754a18b7e7a5e87e5d8da8", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-sm", + "imageId": "sh-32f0b323954a4b13862e9c7a8659d9b8", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9601,9 +9170,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "sm", @@ -9615,20 +9184,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-md", - "imageId": "sh-0b6a5ee6edfd490795e0e5d556f5adf5", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-md", + "imageId": "sh-960dad3ece314a4e897d9df60609f4b4", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9637,9 +9206,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "md", @@ -9651,20 +9220,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-lg", - "imageId": "sh-85dc056b4a9640b68e694a411575a7a3", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-lg", + "imageId": "sh-c06a019c60d341a8bb6432c764dab67d", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9673,9 +9242,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "lg", @@ -9687,20 +9256,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-lgx", - "imageId": "sh-92662de3de1945c286667e0e055a1ed2", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-lgx", + "imageId": "sh-355389b39a5c4603acae2881ea1252be", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9709,9 +9278,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "lgx", @@ -9722,20 +9291,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-xl", - "imageId": "sh-2fcd9144c49943d5b9defd15ace5ded1", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-xl", + "imageId": "sh-ee8361b7ffbd46ea8bfe7ca6b75da39e", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9744,9 +9313,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "xl", @@ -9758,20 +9327,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-2xl", - "imageId": "sh-5844f34d54ec4f149ba32131b5597e02", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-2xl", + "imageId": "sh-9fd4a4fba9984b39a76c87d4ee2f0287", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "desktop", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9780,9 +9349,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "2xl", @@ -9794,20 +9363,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-sm-base", - "imageId": "sh-fccff69701754a18b7e7a5e87e5d8da8", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-sm-base", + "imageId": "sh-32f0b323954a4b13862e9c7a8659d9b8", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9816,9 +9385,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "sm", @@ -9831,20 +9400,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-md-base", - "imageId": "sh-0b6a5ee6edfd490795e0e5d556f5adf5", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-md-base", + "imageId": "sh-960dad3ece314a4e897d9df60609f4b4", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9853,9 +9422,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "md", @@ -9867,20 +9436,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-lg-base", - "imageId": "sh-85dc056b4a9640b68e694a411575a7a3", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-lg-base", + "imageId": "sh-c06a019c60d341a8bb6432c764dab67d", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9889,9 +9458,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "lg", @@ -9903,20 +9472,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-lgx-base", - "imageId": "sh-92662de3de1945c286667e0e055a1ed2", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-lgx-base", + "imageId": "sh-355389b39a5c4603acae2881ea1252be", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9925,9 +9494,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "lgx", @@ -9938,20 +9507,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-xl-base", - "imageId": "sh-2fcd9144c49943d5b9defd15ace5ded1", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-xl-base", + "imageId": "sh-ee8361b7ffbd46ea8bfe7ca6b75da39e", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9960,9 +9529,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "xl", @@ -9974,20 +9543,20 @@ }, { "provider": "freestyle", - "version": "freestyle-cmux-devbox-12975-cloud-cwd-identity-2xl-base", - "imageId": "sh-5844f34d54ec4f149ba32131b5597e02", + "version": "freestyle-cmux-devbox-issue-12346-cloud-browser-open-r4-2xl-base", + "imageId": "sh-9fd4a4fba9984b39a76c87d4ee2f0287", "envVar": "FREESTYLE_SANDBOX_SNAPSHOT", "kind": "base", "cmuxdRemoteCommit": "none-cmux-tui", - "repoCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", + "repoCommit": "15e86f7f8fee8268761e607185ffd40c9dff6a75", "epoch": "2026-09-10-r2", "devboxSource": { "layers": "desktop", - "digest": "d40945ed78a4c3f069ce4e3e32ea91f63acc8bffb824d6578221bfbfcd5d617f", + "digest": "ee5fb0bd7c7b3aa44f4685c57f503e4b6dabe65368510fde81ff48b0ed4fcd61", "schema": 2 }, - "builtAt": "2026-09-19T07:03:33.091Z", - "builderScriptVersion": "a340e3615002cb2a685587753fbf8a8ddaf25a9067d5d14b6e58b01cfb07cc49", + "builtAt": "2026-09-12T02:24:43.178Z", + "builderScriptVersion": "f9384765ede4f4c823ed0d7b7c006c2ab2af5fe743adc3e9a563626ba8b671e0", "agentToolResolvedVersions": { "@anthropic-ai/claude-code": "2.1.267", "@openai/codex": "0.154.0", @@ -9996,9 +9565,9 @@ "agent-browser": "0.37.1" }, "validationStatus": "passed", - "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon f3b652d8dc, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-19 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-fccff69701754a18b7e7a5e87e5d8da8, md=sh-0b6a5ee6edfd490795e0e5d556f5adf5, lg=sh-85dc056b4a9640b68e694a411575a7a3, lgx=sh-92662de3de1945c286667e0e055a1ed2, xl=sh-2fcd9144c49943d5b9defd15ace5ded1, 2xl=sh-5844f34d54ec4f149ba32131b5597e02.", - "cmuxTuiCommit": "f3b652d8dcfbb1f48f23cd8f1afc1d3c58673e8f", - "cmuxTuiSha256": "72b3e77e58a95e308cf4731d24cf4fe56a5a3bebd409e2c6625d38d86d7be1c7", + "notes": "cmux devbox epoch 2026-09-10-r2 Devbox on the Freestyle public platform (api.freestyle.sh) from freestyle/ubuntu-sm: the base's Node/Bun/Python/uv/Docker plus pinned agents, devtools, Chrome + cua-driver, ble.sh devshell, cmux login banner, and the desktop layer (openbox/TigerVNC 5901, noVNC 6901, Ghostty, Chrome, Thunar) run by the cmux-desktop systemd unit as cmux; cmux (uid 1000, NOPASSWD sudo) is the work user and the daemon's session user, so terminals are non-root; hostname cmux (static, live, 127.0.1.1 alias; SSH host keys regenerated under it; journal reset); baked cmux-tui daemon 897bb7a9c0, identity bound to the instance id, no create-time bootstrap. Validated 2026-09-12 with verify-devbox-image.ts by promote-devbox-image.ts (toolchain, agent pins, daemon contract, desktop on 5901/6901). Sizes derived and re-booted by derive-devbox-sizes.ts: sm=sh-32f0b323954a4b13862e9c7a8659d9b8, md=sh-960dad3ece314a4e897d9df60609f4b4, lg=sh-c06a019c60d341a8bb6432c764dab67d, lgx=sh-355389b39a5c4603acae2881ea1252be, xl=sh-ee8361b7ffbd46ea8bfe7ca6b75da39e, 2xl=sh-9fd4a4fba9984b39a76c87d4ee2f0287.", + "cmuxTuiCommit": "897bb7a9c0d7efd6407dbe229d1c30f8b0158589", + "cmuxTuiSha256": "551da27dc459d1d2a399d235a81de8d704d40ad39f91bcc2628aaed99b272f96", "defaultForKind": true, "size": { "name": "2xl", diff --git a/web/tests/vm-devbox-image.test.ts b/web/tests/vm-devbox-image.test.ts index 3966f21e9466..0604ea760835 100644 --- a/web/tests/vm-devbox-image.test.ts +++ b/web/tests/vm-devbox-image.test.ts @@ -32,6 +32,8 @@ import { normalizedDockerfileInstructions, rewriteDevboxAgentPins, } from "../scripts/devbox-image-common"; +import { GUEST_CMUX_OPEN_URL_SCRIPT } from "../services/vms/guestBrowserOpen"; +import { chmodSync, symlinkSync } from "node:fs"; import { DEVBOX_DESKTOP_USER } from "../services/vms/images/desktop"; import { DEVBOX_WORK_HOME, @@ -114,6 +116,7 @@ describe("devbox image template", () => { "cmux-bashrc", "cmux-devbox-boot", "cmux-motd", + "cmux-open-url", "cmux-opencode", "cmux-prompt.bash", "cmux-terminfo.sh", @@ -131,8 +134,7 @@ describe("devbox image template", () => { "cmux-bashrc", "cmux-devbox-boot", "cmux-motd", - "cmux-opencode", - "cmux-prompt.bash", + "cmux-open-url", "cmux-terminfo.sh", "cmux-terminfo.src", "codex-managed.toml", @@ -1116,4 +1118,21 @@ describe("devbox terminfo overlay", () => { expect(dockerfile).toContain("xterm-ghostty; do"); expect(readFileSync(path.join(templateDir, "cmux-terminfo.sh"), "utf8")).toContain("TERMINFO_DIRS=/etc/terminfo:"); }); + test("cloud browser openers are baked and preserve explicit desktop browser tools", () => { + const opener = read("cmux-open-url"); + expect(opener).toBe(GUEST_CMUX_OPEN_URL_SCRIPT); + expect(spawnSync("sh", ["-n", path.join(templateDir, "cmux-open-url")]).status).toBe(0); + expect(opener).toContain("command -v cmux"); + expect(opener).toContain("Open this URL:"); + expect(dockerfile).toContain("COPY cmux-open-url /usr/local/bin/cmux-open-url"); + for (const name of ["xdg-open", "x-www-browser", "sensible-browser"]) { + expect(dockerfile).toContain(`ln -sfn cmux-open-url /usr/local/bin/${name}`); + } + const bake = readScript("build-devbox-freestyle.ts"); + expect(bake).toContain('await put("cmux-open-url", "/usr/local/bin/cmux-open-url", 0o755);'); + expect(readScript("verify-devbox-image.ts")).toContain("cloud-browser-openers-ok"); + expect(read("agent-config.sh")).toContain("export BROWSER=/usr/local/bin/cmux-open-url"); + expect(read("agent-config.sh")).toContain("export GH_BROWSER=/usr/local/bin/cmux-open-url"); + }); + }); diff --git a/web/tests/vm-guest-cli.test.ts b/web/tests/vm-guest-cli.test.ts index 7144928338b9..1b79db3b73b6 100644 --- a/web/tests/vm-guest-cli.test.ts +++ b/web/tests/vm-guest-cli.test.ts @@ -42,6 +42,110 @@ const TERMINAL_ID = "term_0123456789abcdef0123456789abcdef"; // The in-VM `cmux` shim is shipped as driver-written bytes; a syntax error // would surface only inside a live machine, so validate it here. describe("in-VM cmux shim", () => { + test("open submits a URL to the host request stream when a native mirror is attached", () => { + const dir = mkdtempSync(join(tmpdir(), "cmux-guest-open-")); + const shim = join(dir, "cmux"); + const tui = join(dir, "cmux-tui"); + const snapshot = join(dir, "snapshot.json"); + writeFileSync(shim, GUEST_CMUX_SHIM); + chmodSync(shim, 0o755); + writeFileSync( + snapshot, + JSON.stringify({ + clients: [{ client_kind: "native-mirror", attached_terminal_ids: [TERMINAL_ID] }], + tabs: [{ content_kind: "terminal", content_id: TERMINAL_ID, pane_id: "pane_1" }], + browsers: [{ id: "browser_1" }], + }), + ); + writeFileSync( + tui, + `#!/bin/sh +case "$*" in + *"session current snapshot"*) cat "${snapshot}" ;; + *"notification create"*) printf '%s\\n' '{"value":{"notification_id":"notification_1"}}' ;; + *"tab create browser"*) printf '%s\\n' '{"value":{"browser_id":"browser_1"}}' ;; + *) exit 91 ;; +esac +`, + ); + chmodSync(tui, 0o755); + try { + const result = spawnSync("sh", [shim, "open", "https://github.com/login/device"], { + encoding: "utf8", + env: { + NODE_ENV: "test", + HOME: dir, + CMUX_TUI_BIN: tui, + CMUX_TUI_TERMINAL_ID: TERMINAL_ID, + PATH: `${dir}:${process.env.PATH ?? "/usr/bin:/bin"}`, + }, + }); + expect(result.status).toBe(0); + expect(result.stdout).toBe(""); + expect(result.stderr).toBe(""); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + test("open prints a URL and exits successfully when no browser resource is retained", () => { + const dir = mkdtempSync(join(tmpdir(), "cmux-guest-open-fallback-")); + const shim = join(dir, "cmux"); + const tui = join(dir, "cmux-tui"); + writeFileSync(shim, GUEST_CMUX_SHIM); + chmodSync(shim, 0o755); + writeFileSync( + tui, + `#!/bin/sh +case "$*" in + *"session current snapshot"*) printf '%s\\n' '{"browsers":[]}' ;; + *"tab create browser"*) printf '%s\\n' '{"value":{"browser_id":"browser_1"}}' ;; + *) exit 91 ;; +esac +`, + ); + chmodSync(tui, 0o755); + try { + const result = spawnSync("sh", [shim, "open", "https://github.com/login/device"], { + encoding: "utf8", + env: { + NODE_ENV: "test", + HOME: dir, + CMUX_TUI_BIN: tui, + CMUX_TUI_TERMINAL_ID: TERMINAL_ID, + PATH: `${dir}:${process.env.PATH ?? "/usr/bin:/bin"}`, + }, + }); + expect(result.status).toBe(0); + expect(result.stdout).toContain("Open this URL: https://github.com/login/device"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + test("open prints unsupported schemes instead of dropping the URL", () => { + const dir = mkdtempSync(join(tmpdir(), "cmux-guest-open-invalid-")); + const shim = join(dir, "cmux"); + writeFileSync(shim, GUEST_CMUX_SHIM); + chmodSync(shim, 0o755); + try { + const result = spawnSync("sh", [shim, "open", "mailto:user@example.com"], { + encoding: "utf8", + env: { + NODE_ENV: "test", + HOME: dir, + CMUX_TUI_BIN: join(dir, "missing-cmux-tui"), + CMUX_TUI_TERMINAL_ID: TERMINAL_ID, + PATH: `${dir}:${process.env.PATH ?? "/usr/bin:/bin"}`, + }, + }); + expect(result.status).toBe(0); + expect(result.stdout).toContain("Open this URL: mailto:user@example.com"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + test.each(["existing", "create", "create-failed", "missing-id"])("peer exec selects a supported workspace and fails closed (%s)", async (mode) => { const directory = mkdtempSync(join(tmpdir(), "cmux-peer-exec-")); const socket = join(directory, "peer.sock");