Skip to content
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
613caed
Update Everything: one action updates the app, every machine's daemon…
teamleaderleo Oct 18, 2026
4ace32e
chore(next): PR autofix at 613caed1dc12
Oct 18, 2026
ad4e307
Update Everything: its three entry types are structs (namespace lint)
teamleaderleo Oct 18, 2026
b5d67a3
Merge remote-tracking branch 'origin/feat-cmux-next' into recut/updat…
teamleaderleo Oct 18, 2026
2fc1a2e
Update Everything: translate the catalog into all 21 languages
teamleaderleo Oct 18, 2026
64d4162
Merge remote-tracking branch 'origin/feat-cmux-next' into recut/updat…
teamleaderleo Oct 19, 2026
1683682
Merge remote-tracking branch 'origin/feat-cmux-next' into codex/cx-1m…
teamleaderleo Oct 19, 2026
90889de
chore(next): PR autofix at 168368272e98
Oct 19, 2026
b77f2d8
Merge remote-tracking branch 'origin/feat-cmux-next' into codex/cx-1m…
teamleaderleo Oct 19, 2026
77dd4d1
chore(next): regenerate ci-target-graph.json
teamleaderleo Oct 19, 2026
5976f56
chore(next): PR autofix at 77dd4d17f2bd
Oct 19, 2026
282c136
Merge origin/feat-cmux-next into codex/cx-1mbnu-update-everything
teamleaderleo Oct 19, 2026
3ece2c8
test(next): Update Everything relaunch, deferred daemons and launch-c…
teamleaderleo Oct 19, 2026
e355e58
fix(next): Update Everything finishes only after a real relaunch, onc…
teamleaderleo Oct 19, 2026
1e5d6c1
test(next): Update Everything run expects devbox after the relaunch; …
teamleaderleo Oct 19, 2026
59acaf8
Merge origin/feat-cmux-next into codex/cx-1mbnu-update-everything
teamleaderleo Oct 19, 2026
ca42462
Merge origin/feat-cmux-next into codex/cx-1mbnu-update-everything
teamleaderleo Oct 20, 2026
a1d58df
Merge origin/feat-cmux-next into codex/cx-1mbnu-update-everything
teamleaderleo Oct 20, 2026
9471ca7
Merge origin/feat-cmux-next into codex/cx-1mbnu-update-everything
teamleaderleo Oct 20, 2026
98e400f
Merge origin/feat-cmux-next into codex/cx-1mbnu-update-everything
teamleaderleo Oct 20, 2026
d542db0
chore(next): PR autofix at 98e400f4b1b7
Oct 20, 2026
6c76066
Merge feat-cmux-next into codex/cx-1mbnu-update-everything
teamleaderleo Oct 21, 2026
1f102c9
fix(cmux-next): keep SettingsActionCatalog under its god-file size; i…
teamleaderleo Oct 21, 2026
7bf44b0
Merge remote-tracking branch 'origin/feat-cmux-next' into orphan-20940
teamleaderleo Oct 21, 2026
8e71df2
chore(cmux-next): regenerate ci-target-graph.json on the merged head
teamleaderleo Oct 21, 2026
5d16013
chore(cmux-next): take base's action exports; the PR autofix regenera…
teamleaderleo Oct 21, 2026
2484b62
Merge remote-tracking branch 'origin/feat-cmux-next' into adopt-20940
teamleaderleo Oct 21, 2026
38c7577
Update Everything: paired servers stay out by default; updates.update…
teamleaderleo Oct 21, 2026
6bc856b
Update Everything: debug.update_everything and a tour capture its con…
teamleaderleo Oct 21, 2026
63f61d2
ci-target-graph.json: take base's copy; only the base autofix writes it
teamleaderleo Oct 21, 2026
eca67ca
Merge remote-tracking branch 'origin/feat-cmux-next' into adopt-20940
teamleaderleo Oct 21, 2026
103bbec
Merge remote-tracking branch 'origin/feat-cmux-next' into adopt-20940
teamleaderleo Oct 21, 2026
7a2dbd6
chore(next): settings and action exports from PR autofix run 39602257463
teamleaderleo Oct 21, 2026
c242dc8
Merge remote-tracking branch 'origin/feat-cmux-next' into adopt-20940
teamleaderleo Oct 21, 2026
59ae151
fix(next updates): the relaunched app finishes only the machines the …
teamleaderleo Oct 21, 2026
9dd5810
Merge remote-tracking branch 'origin/feat-cmux-next' into adopt-20940
teamleaderleo Oct 21, 2026
4ce25c6
Regenerate generated files from the PR autofix (run 39651226919)
teamleaderleo Oct 21, 2026
897eac6
Merge remote-tracking branch 'origin/feat-cmux-next' into adopt-20940
teamleaderleo Oct 21, 2026
395a5e1
Regenerate generated files from the PR autofix (run 39661203445)
teamleaderleo Oct 21, 2026
78eaf08
Give updates.updateEverythingServers an agent policy (user only)
teamleaderleo Oct 22, 2026
d63c78b
Merge feat-cmux-next (eb423e12) into Update Everything
teamleaderleo Oct 22, 2026
15a20a7
Merge feat-cmux-next (47ae435f) into Update Everything
teamleaderleo Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Packages/macOS/CmuxNext/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ let package = Package(
"CmuxNextRemote",
"CmuxNextMobile",
"CmuxNextUpdater",
"CmuxNextUpdateEverything",
"CmuxNextResources",
"CmuxNextBrowserImport",
"CmuxNextOnboarding",
Expand Down Expand Up @@ -857,6 +858,15 @@ let package = Package(
],
swiftSettings: uiSwiftSettings
),
// Update Everything (cx-1mbnu) and the app's update actions: the plan, confirmation,
// runner, relaunch note, daemon reads and agent CLI steps over an injected
// UpdateEverythingHost (kept out of CmuxNextApp's size cap).
.target(
name: "CmuxNextUpdateEverything",
dependencies: ["CmuxNextActions", "CmuxNextCompat", "CmuxNextDaemon", "CmuxNextDaemonService", "CmuxNextDesign", "CmuxNextRemote", "CmuxNextUpdater"],
resources: [.process("Resources/Localizable.xcstrings")],
swiftSettings: uiSwiftSettings
),
// Cloud machines: auth, REST client, tunnel and link processes. The
// App turns each connected machine's link socket into a DaemonService.
.target(
Expand Down Expand Up @@ -1184,6 +1194,7 @@ let package = Package(
name: "CmuxNextAppTests",
dependencies: ["CmuxNextCompat", "CmuxNextFileGrid", "CmuxNextScreenshots", "CmuxNextTerminalGeometry", "CmuxNextRemote", "CmuxNextWakeups", "CmuxNextNowPlaying", "CmuxNextFloating", "CmuxNextLayout", "CmuxNextTasks", "CmuxNextAgentCursor", "CmuxNextTabs", "CmuxNextProcessEnvironment", "CmuxNextApp", "CmuxNextApps", "CmuxNextCookieImport", "CmuxNextAgentLanes", "CmuxNextBrowserHistory", "CmuxNextCrashReporting", "CmuxNextDiff", "CmuxNextActions", "CmuxNextHistory", "CmuxNextCopyMode",
"CmuxNextComputerUse", "CmuxNextDaemon", "CmuxNextDaemonRecovery", "CmuxNextDaemonService", "CmuxNextRemoteLocalhostService", "CmuxNextFocus", "CmuxNextFocusUI", "CmuxNextHome", "CmuxNextHomeEngine", .product(name: "CmuxHomeCore", package: "CmuxHomeCore"),
"CmuxNextUpdateEverything",
.product(name: "CmuxHomeRender", package: "CmuxHomeRender"),
// ReduceMotionLoopTests builds the vendored Home typing indicators.
.product(name: "MessagesLabHome", package: "CmuxMessagesLab"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78750,6 +78750,137 @@
}
}
},
"action.updates.updateEverything": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Update Everything…"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Alles aktualisieren …"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Tout mettre à jour…"
}
},
"ar": {
"stringUnit": {
"state": "translated",
"value": "تحديث كل شيء…"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Actualizar todo…"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "全部更新…"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "全部更新…"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "모두 업데이트…"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "すべてをアップデート…"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "Ažuriraj sve…"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "Opdater alt…"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "Aggiorna tutto…"
}
},
"km": {
"stringUnit": {
"state": "translated",
"value": "ធ្វើបច្ចុប្បន្នភាពទាំងអស់…"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "Oppdater alt …"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "Zaktualizuj wszystko…"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "Atualizar Tudo…"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Обновить всё…"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "อัปเดตทั้งหมด…"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "Her Şeyi Güncelle…"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Оновити все…"
}
},
"vi": {
"stringUnit": {
"state": "translated",
"value": "Cập nhật tất cả…"
}
}
}
},
"action.window.closeOthers": {
"extractionState": "manual",
"localizations": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ nonisolated extension ActionSurfaceCatalog {
"palette.toggleFullWidthTab", "tab.showResources", "workspace.showResources",
"toggleBrowserDeveloperTools", "showBrowserJavaScriptConsole", "inspectBrowserElement", "browserPrint",
"toggleBrowserFocusMode", "toggleBrowserDesignMode", "toggleReactGrab", "palette.browserToggleOmnibar",
"importFromBrowser", "password.importCSV", "browser.importFromFiles", "browser.allowAgentWithExtensions", "browserProfile.delete", "openLinkInDefaultBrowser", "filePreviewOpenWith", "filePreviewOpenExternally",
"importFromBrowser", "password.importCSV", "browser.importFromFiles", "browser.allowAgentWithExtensions", "browserProfile.delete", "updates.updateEverything", "openLinkInDefaultBrowser", "filePreviewOpenWith", "filePreviewOpenExternally",
"filePreviewRevealInFinder", "palette.vscodeServeWebStop", "palette.vscodeServeWebRestart",
"browser.pageInfo", "browser.pageInfo.connection", "browser.pageInfo.certificate",
"browser.pageInfo.cookies", "browser.pageInfo.manageSiteData", "browser.pageInfo.siteSettings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ nonisolated extension ActionSurfaceCatalog {
"palette.openGhosttySettings", "ghostty.showDiagnostics", "palette.makeDefaultBrowser", "palette.makeDefaultTerminal",
"palette.toggleSetting", "palette.shortcutKeymap", "palette.searchShortcuts", "palette.installCLI",
"palette.uninstallCLI", "palette.restartSocketListener", "palette.checkForUpdates", "updates.whatsNew", "announcements.show", "announcements.hide",
"palette.applyUpdateIfAvailable", "palette.switchAppChannel", "palette.pro.upgrade",
"palette.applyUpdateIfAvailable", "palette.switchAppChannel", "palette.pro.upgrade", "updates.updateEverything",
"sendFeedback", "help.featureFlags", "help.documentation", "help.showCrashLogs",
"appearance.density.compact", "appearance.density.comfortable", "appearance.animationSpeed.fast",
"appearance.animationSpeed.normal", "appearance.animationSpeed.off", "appearance.animations.newTab.toggle", "appearance.animations.newWorkspace.toggle", "appearance.animations.bounce.subtle", "appearance.animations.bounce.none",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ nonisolated enum SettingsActionCatalog: ActionCatalogGroup {
keywords: ["update", "install"], category: .settings, symbol: "arrow.down.circle.fill",
surfaces: [.palette, .menu], cliName: "settings install-available-update", mainMenu: .app
),
updateEverything,
ActionDescriptor(
id: "palette.attemptUpdate",
title: String(localized: "action.palette.attemptUpdate", defaultValue: "Attempt Update", bundle: .module),
Expand Down Expand Up @@ -417,4 +418,17 @@ nonisolated enum SettingsActionCatalog: ActionCatalogGroup {
),
]
}

/// Update Everything (cx-1mbnu): the app, every connected machine's cmux and agent CLIs, then
/// the threads. Only a person starts it: it restarts daemons, agents and the app.
private static var updateEverything: ActionDescriptor {
var descriptor = ActionDescriptor(
id: "updates.updateEverything",
title: String(localized: "action.updates.updateEverything", defaultValue: "Update Everything…", bundle: .module),
keywords: ["update", "upgrade", "machines", "agents", "claude", "codex", "daemon", "sparkle"], category: .settings,
symbol: "arrow.triangle.2.circlepath.circle", surfaces: [.palette, .menu], mainMenu: .app
)
descriptor.isPersonOnly = true
return descriptor
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ nonisolated struct CloudMoveCLI: Sendable {
}

/// `cmux link machine update`: updates the cmux-tui of the machine whose daemon `socket`
/// reaches to this app's own pinned build, when that machine is outdated (cx-rppd.13).
func machineUpdate(socket: String) async throws -> Answer {
try await run(["link", "machine", "update", "--socket", socket], timeout: max(timeout, Self.machineUpdateTimeout))
/// reaches to this app's own pinned build, when that machine is outdated (cx-rppd.13), or with `toBundled` whenever its build differs (cx-1mbnu).
func machineUpdate(socket: String, toBundled: Bool = false) async throws -> Answer {
try await run(["link", "machine", "update", "--socket", socket] + (toBundled ? ["--to-bundled"] : []), timeout: max(timeout, Self.machineUpdateTimeout))
}

/// Longer than `link machine update`'s own wait for the machine (25 minutes) plus a first download.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import AppKit
import CmuxNextActions
import CmuxNextSettings
import CmuxNextTerminal
import CmuxNextUpdateEverything
import os

/// Settings and help actions (category `settings`, except appearance, see
Expand Down Expand Up @@ -38,6 +39,7 @@ enum SettingsHandlers {
ChangelogPageTab.register(context.services)
UpdateHandlers.bind(into: registry, updater: context.services.updater,
openWhatsNew: { [weak services = context.services] in services.map { WhatsNewPage.open($0) } ?? false })
UpdateEverything.bind(into: registry, host: .app(context.services))
OnboardingHandlers.bind(into: registry, context: context)
CLIInstallHandlers.bind(into: registry, context: context)
KeymapHandlers.bind(into: registry, context: context)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import AppKit
import CmuxNextDaemonService
import CmuxNextUpdateEverything

extension UpdateEverythingHost {
/// Update Everything over the App's services (cx-1mbnu); the rest lives in its module.
static func app(_ services: AppServices) -> Self {
let machines = services.machines
return Self(local: { machines.local }, remotes: {
machines.ssh.filter { $0.daemon.connection != nil }.map { ($0.machineID, $0.host.label, .ssh, $0.daemon) }
+ machines.cloud.filter { $0.daemon.connection != nil }.map { ($0.machineID, $0.machine.title, .cloud, $0.daemon) }
}, daemon: { machines.anyDaemon(machine: $0) }, installSSH: { id in
guard let session = machines.sshSession(id) else { throw ActionFailure(message: UpdateEverythingMachine.disconnected) }
try await services.ssh.install(session)
}, updateCloud: { socket in
// Gated like SSH's install: turned off by the organization (or no managed team) refuses.
if let refusal = services.cloud.unavailableReason { throw ActionFailure(message: refusal) }
guard let binary = services.cloud.move.cli else { throw ActionFailure(message: UpdateEverythingMachine.disconnected) }
let answer = try await CloudMoveCLI(binary: binary).machineUpdate(socket: socket, toBundled: true)
if !answer.ok { throw ActionFailure(message: UpdateEverythingMachine.failureText(answer.error)) }
return answer.updated
}, updater: services.updater, appVersion: Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String,
window: { services.windows.active?.window ?? NSApp.mainWindow },
journal: UpdateEverythingDaemon.journalURL(bundleID: services.environment.launch.bundleID))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ public struct DaemonCapabilities: Sendable {
/// permissions through that machine's session daemon (cmux-tui/spec/commands.md
/// "Agent session attach").
public let agentSessionAttach = "agent-session-attach-v1"
/// `agent-update-plan` / `agent-update-apply`: a machine's outdated agent CLIs, their update and
/// its chats' restart on them (Update Everything, cmux-tui/spec/commands.md "Agent CLI updates").
public let agentUpdate = "agent-update-v1"
/// `agent-session-start`: the session daemon starts a new chat for one of its agent tabs in
/// its own machine's acpmux (policy `ask`) and binds it (cmux-tui/spec/commands.md
/// "agent-session-start"). A chat in a remote workspace starts only through it; without it
Expand Down Expand Up @@ -432,7 +435,7 @@ public struct DaemonCapabilities: Sendable {
terminalCommandHistory, dockColumns, edgeDocks, dockColumnRole, pinnedDock, rows, tabColumnRespawn, endTerminalsKeepLayout, stateResources,
sessionIdentity, localConversations, conversationDraftEvents, chiefLive, cloudChiefLive, conversationRuns, cloudConversationRuns, cloudConversationTyping, tabSplitRespawn, tabSplitKeepFocus, frontendBrowserHistory, frontendBrowserTabProfile,
attachIdentity, creationReceipts, creationAttemptKeys, terminalColorOverrides,
workspaceKind, workspaceAgentFolder, workspaceAgentStart, conversationTabs, agentSessionTabs, agentSessionAttach, agentSessionStart, agentSessionStartKeyedReason, agentSessionTrust, pageTabs, pageTabRoute, windowRecords, appScreens, conversationSearch, conversationThreads, conversationThread, conversationInboxState, homeInbox, chiefEvents, cloudConversations, cloudHostReplica, pageCredential, scopedPageCredential, wsListen, wsListenOrigins, localAttachments,
workspaceKind, workspaceAgentFolder, workspaceAgentStart, conversationTabs, agentSessionTabs, agentSessionAttach, agentSessionStart, agentSessionStartKeyedReason, agentSessionTrust, agentUpdate, pageTabs, pageTabRoute, windowRecords, appScreens, conversationSearch, conversationThreads, conversationThread, conversationInboxState, homeInbox, chiefEvents, cloudConversations, cloudHostReplica, pageCredential, scopedPageCredential, wsListen, wsListenOrigins, localAttachments,
tabWorkspaceName, terminalSnapshotHistory, terminalSnapshotLocalHistory, terminalSnapshotImages,
terminalClipboardRead, personalMixedOrder, sidebarLayout, sidebarDoc, placementShift, paletteUsage, browserDevices, browserFit, browserFitWidth, projectList, splitClientKeys, splitClientSplitID, conversationTabSplit, terminalInputWhileCreating,
workspaceGroupIcon, workspaceGroupPin, screenGroupIcon, tabGroupIcon, sessionIcon, chiefInspect, feedLocalOwner, remoteTerminalTabs, detachedTerminals, tabRestart, tabRestartKeptLayout,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import Foundation

/// `agent-update-plan {}`: the machine's agent CLIs that are outdated and that its acpmux can
/// update without sudo, and its threads (Update Everything's confirmation).
public struct AgentUpdatePlanRequest: DaemonRequest {
public struct Harness: Decodable, Sendable, Equatable {
public var id: String
/// The harness's display name, when its profile has one.
public var name: String?
public var version: String?
public var latest: String?
}

public struct Response: Decodable, Sendable, Equatable {
public var harnesses: [Harness]
/// Threads with a running agent.
public var liveThreads: Int
/// Threads in a turn now.
public var runningThreads: Int

enum CodingKeys: String, CodingKey {
case harnesses
case liveThreads = "live_threads"
case runningThreads = "running_threads"
}
}

public static let command = "agent-update-plan"
/// The capability of a daemon that updates its machine's agent CLIs (`agent-update-v1`,
/// cmux-tui/spec/commands.md agent-update-plan / agent-update-apply): it asks the machine's own
/// acpmux, so Update Everything reaches this Mac, SSH and Cloud machines the same way.
public static let capability = "agent-update-v1"
public static var requiredCapability: String? { capability }
public init() {}
}

/// `agent-update-apply {ids, continue_threads}`: runs the acpmux's own update of each named
/// harness that is still outdated and waits for them; with `continue_threads`, then restarts each
/// live thread on its harness's new version, resuming its conversation (a thread in a turn
/// restarts when the turn ends). No command travels: the machine builds every update itself.
public struct AgentUpdateApplyRequest: DaemonRequest {
public struct Result: Decodable, Sendable, Equatable {
public var id: String
public var ok: Bool
/// The version after the update.
public var version: String?
/// Why it failed (the updater's last output line).
public var error: String?
}

public struct Response: Decodable, Sendable, Equatable {
public var results: [Result]
/// Threads restarted now (they were idle).
public var threadsRestarted: Int
/// Threads that restart when their running turn ends.
public var threadsAfterTurn: Int
/// Threads that could not restart and still run the old agent (nil from an older daemon).
public var threadsFailed: Int?
/// Why threads could not restart: the restart call's error, or the first thread's.
public var threadsError: String?

enum CodingKeys: String, CodingKey {
case results
case threadsRestarted = "threads_restarted"
case threadsAfterTurn = "threads_after_turn"
case threadsFailed = "threads_failed"
case threadsError = "threads_error"
}
}

public static let command = "agent-update-apply"
public static var requiredCapability: String? { AgentUpdatePlanRequest.capability }
/// The longest an apply may take: each updater may run 15 minutes (acpmux `probe.rs`).
public static let timeout: Duration = .seconds(30 * 60)
Comment on lines +73 to +74

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Valid updates time out early

AgentUpdateApplyRequest.timeout is fixed at 30 minutes. The daemon runs updaters one at a time, allowing 15 minutes each plus a margin, up to two hours, followed by chat restarts. Three slow but valid updates can outlast the app's deadline. The app reports failure and can move on to its own relaunch while the daemon is still updating. Match the daemon's full deadline, including chat restarts.


public var ids: [String]
public var continueThreads: Bool

public init(ids: [String], continueThreads: Bool) {
self.ids = ids
self.continueThreads = continueThreads
}

enum CodingKeys: String, CodingKey {
case ids
case continueThreads = "continue_threads"
}
}
Loading