Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4b46bca
acpmux: require version evidence for already-current updates
teamleaderleo Oct 19, 2026
4f309ff
agents: refresh inventory versions without probing or updating
teamleaderleo Oct 19, 2026
c1819be
agents: connect explicit version refresh to the selected owner
teamleaderleo Oct 19, 2026
b4472f9
Add owner update terminal jobs with durable replay
teamleaderleo Oct 19, 2026
2f04b04
Preserve app mutation keys through shared action runs
teamleaderleo Oct 19, 2026
3fddabe
Preserve app callers through the shared action registry
teamleaderleo Oct 19, 2026
239e427
Share agent CLI update UI across Settings and actions
teamleaderleo Oct 19, 2026
f8390f6
Require trusted page context for Settings agent updates
teamleaderleo Oct 19, 2026
ff11579
Merge remote-tracking branch 'origin/feat-cmux-next' into HEAD
teamleaderleo Oct 19, 2026
0aae74c
Merge feat-cmux-next into the agent-update chain after #21398 landed
teamleaderleo Oct 19, 2026
0309542
chore(next): PR autofix at 0aae74c95fc8
Oct 19, 2026
4df7468
Merge remote-tracking branch 'pr/21439new' into HEAD
teamleaderleo Oct 19, 2026
14000c7
Merge feat-cmux-next into the agent-update chain
teamleaderleo Oct 19, 2026
bb34af6
Merge commit '0c2d0e8719258b410e8e8d0d14ba5d0c078140b6' into HEAD
teamleaderleo Oct 19, 2026
4982e18
Merge commit 'bb34af663b08b741b6618d893784653625b33d5e' into HEAD
teamleaderleo Oct 19, 2026
78baffe
Merge commit '4982e18eb88eb12df0183db356c72c677bb8b5b4' into HEAD
teamleaderleo Oct 19, 2026
be7ccaf
Merge commit '78baffe6020eda39ff8a2d86fb250a929c213359' into HEAD
teamleaderleo Oct 19, 2026
db6c520
Merge commit 'be7ccaf4e41f044e41535aaae50819e74193a01a' into HEAD
teamleaderleo Oct 19, 2026
bff87ce
Merge commit 'db6c520f18289ab1310bc43d0bf1755473aa67f1' into HEAD
teamleaderleo Oct 19, 2026
6460e0a
Merge remote-tracking branch 'origin/feat-cmux-next' into HEAD
teamleaderleo Oct 19, 2026
79130e8
Merge commit '6460e0a6e9178f02b705fdc1e086be1ee45abcc2' into HEAD
teamleaderleo Oct 19, 2026
45adaef
Bind the Settings update scope with withValueInTask
teamleaderleo Oct 19, 2026
37083aa
chore(next): PR autofix at 45adaef20fc3
Oct 19, 2026
a6975b6
Merge origin/feat-cmux-next into #21447
teamleaderleo Oct 19, 2026
a7a6e6a
Merge commit 'a827da19e2f9d3bcfb87ed8fc4d369843b7761dd' into HEAD
teamleaderleo Oct 19, 2026
c3fa200
test(next): agent CLI updates survive reconnects, unknown exits, owne…
teamleaderleo Oct 19, 2026
cdb706e
fix(next): agent CLI update jobs follow their owner across reconnects…
teamleaderleo Oct 19, 2026
c341860
Merge remote-tracking branch 'origin/feat-cmux-next' into HEAD
teamleaderleo Oct 20, 2026
fa5f717
test(next): agent update survives a reconnect mid-batch and a lost st…
teamleaderleo Oct 20, 2026
124684a
fix(next): agent update start keeps its captured owner and replays lo…
teamleaderleo Oct 20, 2026
2a7686e
test(next): a busy answer to a replayed agent CLI update start keeps …
teamleaderleo Oct 20, 2026
4d8358a
fix(next): a busy answer to a replayed agent CLI update start keeps i…
teamleaderleo Oct 20, 2026
99a29e6
Merge branch 'feat-cmux-next' into codex/agent-update-shared-ui-b9db4d92
teamleaderleo Oct 20, 2026
66c6399
chore(next): PR autofix at 99a29e6f4d3a
Oct 20, 2026
b1eada9
Merge remote-tracking branch 'origin/feat-cmux-next' into HEAD
teamleaderleo Oct 20, 2026
459ffa8
Merge remote-tracking branch 'origin/feat-cmux-next' into HEAD
teamleaderleo Oct 20, 2026
de6a736
Merge feat-cmux-next into codex/agent-update-shared-ui-b9db4d92
teamleaderleo Oct 20, 2026
3b5a7ca
Merge feat-cmux-next into codex/agent-update-shared-ui-b9db4d92
teamleaderleo Oct 20, 2026
db34702
chore(next): PR autofix at 3b5a7cad2bea
Oct 20, 2026
7502ddf
test(next): settled owner failures and ended jobs never block agent C…
teamleaderleo Oct 20, 2026
433979f
Merge feat-cmux-next into codex/agent-update-shared-ui-b9db4d92
teamleaderleo Oct 20, 2026
46ae1f5
Merge branch 'feat-cmux-next' into codex/agent-update-shared-ui-b9db4d92
teamleaderleo Oct 20, 2026
0ad8305
fix(next): settle agent CLI updates the owner answered, and check end…
teamleaderleo Oct 20, 2026
0902535
test(next): a coded answer to a replayed agent CLI update start keeps…
teamleaderleo Oct 20, 2026
445644e
fix(next): only a refusal settles a replayed agent CLI update start
teamleaderleo Oct 20, 2026
135d115
Merge feat-cmux-next (d3a9df0c733d) into codex/agent-update-shared-ui…
teamleaderleo Oct 20, 2026
47f662a
test(next): an indeterminate first agent CLI update start keeps block…
teamleaderleo Oct 20, 2026
bba7226
fix(next): an indeterminate first agent CLI update start keeps its job
teamleaderleo Oct 20, 2026
6f00b00
Merge feat-cmux-next (550ef1d99f6c) into codex/agent-update-shared-ui…
teamleaderleo Oct 20, 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 @@ -66,6 +66,8 @@ import PackageDescription
// tab or its front tab, and the one-time rename; pure rules; the App supplies its pages and the rename command)
// CmuxNextAgentActivity -> Design (Agent activity pane: computer use sessions, timeline, prototype layouts;
// a projection of the CUA host; no daemon; the App supplies the source; plans/cmux-next/computer-use.md)
// CmuxNextAgentUpdates -> Actions, Daemon, DaemonService, Pages, Settings (Settings and palette agent CLI
// updates as owner-bound terminal jobs: intents, receipts, outcomes; the App supplies the route glue)
// CmuxNextFileGrid -> system frameworks only (the thumbnail grid: tiles, thumbnail cache, drag
// out, drop reading, Quick Look and the command menu; it reads and changes no files)
// CmuxNextScreenshots -> FileGrid, Files (the Screenshots pane: source detection for macOS and the
Expand Down Expand Up @@ -209,6 +211,7 @@ let package = Package(
"CmuxNextHomeEngine",
"CmuxNextHostedChief",
"CmuxNextDaemonService",
"CmuxNextAgentUpdates",
"CmuxNextFocus",
"CmuxNextFocusUI",
"CmuxNextComputerUse",
Expand Down Expand Up @@ -1235,12 +1238,20 @@ let package = Package(
dependencies: ["CmuxNextWakeups", "CmuxNextCompat", "CmuxNextControl", "CmuxNextActions", "CmuxNextSettings", "CmuxNextDaemon"],
swiftSettings: daemonSwiftSettings
),
// Settings and palette agent CLI updates (moved out of CmuxNextApp/AgentHarnesses):
// the shared registry handler, owner routes, intents and terminal outcomes.
.target(
name: "CmuxNextAgentUpdates",
dependencies: ["CmuxNextActions", "CmuxNextDaemon", "CmuxNextDaemonService", "CmuxNextPages", "CmuxNextSettings"],
swiftSettings: uiSwiftSettings
),
.testTarget(
name: "CmuxNextAppTests",
dependencies: [ // sorted: scripts/cmux-next/check-package-dependency-lists.py
"CmuxNextActions",
"CmuxNextAgentCursor",
"CmuxNextAgentLanes",
"CmuxNextAgentUpdates",
"CmuxNextApp",
"CmuxNextApps",
"CmuxNextBrowserHistory",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ public nonisolated enum ActionWorkRefusal: Sendable, Hashable {
/// tasks the handlers reported with `track(_:)`. Keyboard, menu, and
/// palette runs capture nothing, so tracking costs them nothing.
extension ActionRegistry {
/// Whether a caller awaits the work handlers report (`capturingWork`). A handler whose
/// failure would otherwise go unseen (a palette run) shows it itself when this is false.
public var isCapturingWork: Bool { capturedWork != nil }

/// Reports a task a handler started. Ignored unless a caller is capturing.
public func track(_ task: ActionWork) {
if capturedWork != nil { capturedWork?.append(task) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ nonisolated enum AgentHarnessActionCatalog: ActionCatalogGroup {
ActionEnumCase(value: "acp", title: t("argument.agent.harness.protocol.acp", "ACP")),
ActionEnumCase(value: "terminal", title: t("argument.agent.harness.protocol.terminal", "Terminal")),
])
return [
var actions = [
// Without arguments (the palette, the model picker's +) it opens Settings > Agents >
// Add; with a command, registry agent or example it writes the profile.
ActionDescriptor(
Expand Down Expand Up @@ -63,17 +63,21 @@ nonisolated enum AgentHarnessActionCatalog: ActionCatalogGroup {
cliName: "agent check-harness",
surfacePlan: ActionSurfacePlan(cli: .offered, contextMenuExemption: .noObject)
),
// Agent CLI health (cx-ny85): types every outdated agent CLI's own update into a
// terminal tab, like Settings > Agents > Update All; with nothing to update (or no
// window) it opens Settings > Agents.
// The native owner starts visible terminal jobs. Settings passes a profile id;
// the palette updates each observed outdated family on its captured machine.
ActionDescriptor(
id: "agent.harness.updateAll", title: t("action.agent.harness.updateAll", "Update Agent CLIs"),
keywords: ["agent", "harness", "cli", "update", "upgrade", "version", "outdated", "npm", "brew", "codex", "claude", "gemini"],
category: .agents, symbol: "arrow.down.circle",
arguments: [id],
// A terminal tab the person watches: scripts run the updates themselves.
surfacePlan: ActionSurfacePlan(cli: .exempt(.guiOnly), contextMenuExemption: .noObject)
),
]
for index in actions.indices where actions[index].id == "agent.harness.updateAll" {
actions[index].isPersonOnly = true
}
return actions
}

private static func t(_ key: StaticString, _ english: String.LocalizationValue) -> String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import CmuxNextActions
import CmuxNextDaemon
public import CmuxNextDaemonService

extension AgentCLIUpdateActions {
/// The app's daemons, read when an update captures its owner.
public struct Daemons {
let active: @MainActor () -> DaemonService?
let machine: @MainActor (String) -> DaemonService?

public init(active: @escaping @MainActor () -> DaemonService?, machine: @escaping @MainActor (String) -> DaemonService?) {
self.active = active
self.machine = machine
}
}

/// The palette's target machine, else the active daemon, while the app still routes to it.
public convenience init(daemons: Daemons, unsupported: @escaping @MainActor () -> String,
nothingToUpdate: @escaping @MainActor () -> String,
stillUpdating: @escaping @MainActor (String) -> String, localMachine: String) {
self.init(capture: { invocation in
let registered: @MainActor (DaemonService) -> Bool = { daemons.machine($0.machineID) === $0 }
if let target = invocation.target {
guard target.kind == .machine, let daemon = daemons.machine(target.id) else { throw DaemonError.notConnected }
return try Route.capture(daemon, registered: registered)
}
guard let daemon = daemons.active() else { throw DaemonError.notConnected }
return try Route.capture(daemon, registered: registered)
}, unsupported: unsupported, nothingToUpdate: nothingToUpdate, stillUpdating: stillUpdating, localMachine: localMachine)
}
}

extension AgentCLIUpdateActions.Route {
/// Captures one daemon's connected machine/session owner and connection epoch.
/// `registered` says whether the app still routes that machine to this daemon.
public static func capture(_ daemon: DaemonService, registered: @escaping @MainActor (DaemonService) -> Bool) throws -> Self {
guard registered(daemon), !daemon.policyBlock.isBlocked, case .connected = daemon.store.connectionState,
let connection = daemon.connection, let identity = daemon.identity,
let machine = identity.machineResourceID, let session = identity.sessionResourceID else {
throw DaemonError.notConnected
}
let epoch = daemon.store.connectionEpoch
let current: @MainActor () -> Bool = { [weak daemon] in
guard let daemon, registered(daemon), !daemon.policyBlock.isBlocked, case .connected = daemon.store.connectionState else {
return false
}
return daemon.connection === connection && daemon.store.connectionEpoch == epoch
&& daemon.identity?.machineResourceID == machine && daemon.identity?.sessionResourceID == session
}
var carrier: AgentCLIJobCarrier?
// The owner-checked carrier, opened by whichever call comes first: a route re-captured
// after a reconnect starts (a replayed key) or observes without reading the inventory.
let opened: @MainActor () async throws -> AgentCLIJobCarrier = {
guard current() else { throw DaemonError.notConnected }
if let carrier { return carrier }
let captured = try await connection.agentCLIJobs(expectedMachineID: machine, expectedSessionID: session)
guard current() else { throw DaemonError.notConnected }
carrier = captured
return captured
}
return Self(machine: machine, session: session, isCurrent: current, inventory: {
try await opened().inventory()
}, start: { cli, key in
try await opened().startUpdate(cli: cli, idempotencyKey: key)
}, observe: { receipt in
try await opened().observe(receipt)
})
}
}
Loading