Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d3b2da0
cloud: bump the devbox agent pins to the current releases, add bubble…
lawrence703 Sep 10, 2026
40a98b0
cloud: promote --replay and --sizes-result; judge only the written ro…
lawrence703 Sep 10, 2026
6b7cf21
cloud: promote the cmux-devbox-agents0909 desktop and base ladders (e…
lawrence703 Sep 10, 2026
bad56e2
Merge remote-tracking branch 'origin/main' into feat-devbox-snapshot-…
lawrence703 Sep 10, 2026
d6a2769
Merge origin/main (#12243: desktop-by-default, wsboot ladders) into f…
lawrence703 Sep 10, 2026
c9af130
cloud: source digest schema 2 covers the bake script and the Dockerfi…
lawrence703 Sep 10, 2026
ce0efa9
Merge remote-tracking branch 'origin/main' into feat-devbox-snapshot-…
lawrence703 Sep 10, 2026
c690d87
cloud: source digest hashes every bake-script line; upgrade the promo…
lawrence703 Sep 10, 2026
e395e02
cloud: the source-schema upgrade proves the Dockerfile at the entry's…
lawrence703 Sep 10, 2026
496ea22
cloud: devbox README states that bake-script comments are part of the…
lawrence703 Sep 10, 2026
6d735f0
cloud: pass a manifest entry's repoCommit to git as an argument, neve…
lawrence703 Sep 10, 2026
4a02de4
cloud: the git-backed Dockerfile lookup test asserts on HEAD uncondit…
lawrence703 Sep 10, 2026
b976585
test: require one devbox snapshot ladder and no machine kind switcher
austinywang Sep 10, 2026
297c4e9
test: report display capability for legacy machine create requests
austinywang Sep 10, 2026
1b2ef5f
fix: restore one new-machine flow and unify the devbox snapshot ladder
austinywang Sep 10, 2026
54427e2
fix: place CLI help translations at the catalog root
austinywang Sep 10, 2026
8cc8f7c
test: use the repository supported Bun test API
austinywang Sep 10, 2026
2e2d12f
Merge origin/main (#12101: cmux Cloud terminals run as cmux, not root…
lawrence703 Sep 10, 2026
2562763
cloud: promote the cmux-devbox-agents0910 ladder for both kinds (epoc…
lawrence703 Sep 10, 2026
86dd2f4
test: cover agent PTY readiness, failure and cancellation
austinywang Sep 10, 2026
577be74
fix: supervise agent readiness through PTY output and a deadline
austinywang Sep 10, 2026
fffcddf
fix: preserve cancellation and align cloud verification fixtures
austinywang Sep 10, 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
6 changes: 6 additions & 0 deletions .github/workflows/cloud-vm-image-contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ on:
- web/scripts/derive-devbox-sizes.ts
- web/scripts/promote-devbox-image.ts
- web/scripts/validate-devbox-ladder.ts
- web/scripts/build-devbox-freestyle.ts
- web/scripts/verify-devbox-image.ts
- web/scripts/check-devbox-agent-pins.ts
- web/services/vms/images/manifest.json
- web/services/vms/images/sizes.ts
- web/services/vms/images/devbox/**
Expand All @@ -24,6 +27,9 @@ on:
- web/scripts/derive-devbox-sizes.ts
- web/scripts/promote-devbox-image.ts
- web/scripts/validate-devbox-ladder.ts
- web/scripts/build-devbox-freestyle.ts
- web/scripts/verify-devbox-image.ts
- web/scripts/check-devbox-agent-pins.ts
- web/services/vms/images/manifest.json
- web/services/vms/images/sizes.ts
- web/services/vms/images/devbox/**
Expand Down
47 changes: 19 additions & 28 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4364,17 +4364,15 @@ struct CMUXCLI {
guard let gib = Int(number), (4...256).contains(gib), gib % 4 == 0 else { return nil }
return gib * 1024
}
/// The kind the caller explicitly requested, or nil when no kind flag was
/// given (the create verbs then send `VMMachineKind.defaultKind`, a desktop).
static func parseExplicitCloudVMKindFlag(_ args: [String], command: String) throws -> VMMachineKind? {
/// All create verbs provision the same devbox. Legacy kind flags remain
/// accepted; contradictory flags still report the caller's mistake.
static func cloudVMCreateKind(_ args: [String], command: String) throws -> VMMachineKind {
let requestsBase = args.contains("--base") || args.contains("--no-desktop")
let requestsDesktop = args.contains("--desktop")
if requestsBase && requestsDesktop {
throw CLIError(message: "\(command): choose one of --base or --desktop")
}
if requestsBase { return .base }
if requestsDesktop { return .desktop }
return nil
return VMMachineKind.defaultKind
}
private static let cloudVMDesktopPort = 6901
/// Whether a machine payload (`vm.create` / `vm.status` / `vm.base_open`
Expand Down Expand Up @@ -5959,9 +5957,7 @@ struct CMUXCLI {
// away from (the New Machine sheet) does not yank them back when it lands.
let focus = try parseCloudVMFocusOption(focusOpt, command: "vm new")
let detach = hasFlag(rem2, name: "--detach") || hasFlag(rem2, name: "-d")
// A bare `vm new` asks for a machine with a screen; `--base` (alias
// `--no-desktop`) is the explicit shell-only choice (#12239).
let requestedKind = try Self.parseExplicitCloudVMKindFlag(rem2, command: "vm new")
let machineKind = try Self.cloudVMCreateKind(rem2, command: "vm new")
let (sizeOpt, rem3) = parseOption(rem2, name: "--size")
let memoryMb: Int?
if let sizeOpt {
Expand All @@ -5978,18 +5974,16 @@ struct CMUXCLI {
memoryMb = nil
}
let remaining = rem3.filter { !["--detach", "-d", "--desktop", "--base", "--no-desktop"].contains($0) }
// The kind is what the CLI asks for; the backend picks the image. A desktop
// The kind is what the CLI asks for; the backend picks the image. The
// machine gets its screen streamed into a browser split beside the shell.
let machineKind = requestedKind ?? VMMachineKind.defaultKind
let machineName = nameOpt?.trimmingCharacters(in: .whitespacesAndNewlines)
if let unknown = remaining.first(where: { Self.isUnknownFlagToken($0, allowedShortFlags: ["-d"]) }) {
throw CLIError(message: """
vm new: unknown flag '\(unknown)'.

Known flags:
--desktop machine with a VNC screen (the default)
--base shell-only machine, no screen (alias --no-desktop)
--size <4g|8g|16g|24g|32g|64g>
--desktop, --base \(String(localized: "cli.vm.help.legacyKindFlags", defaultValue: "accepted for older scripts; every machine has a screen"))
--name <label> display label (the id stays the address)
--image <image-id> explicit image override (normally omit)
--provider <provider>
Expand Down Expand Up @@ -13170,7 +13164,7 @@ struct CMUXCLI {
let (focusOpt, rem1) = parseOption(rem0a, name: "--focus")
let focus = try parseCloudVMFocusOption(focusOpt, command: "vm base open")
let detach = hasFlag(rem1, name: "--detach") || hasFlag(rem1, name: "-d")
let baseKind = try Self.parseExplicitCloudVMKindFlag(rem1, command: "vm base open") ?? VMMachineKind.defaultKind
let baseKind = try Self.cloudVMCreateKind(rem1, command: "vm base open")
let remaining = rem1.filter { !["--detach", "-d", "--desktop", "--base", "--no-desktop"].contains($0) }
if let unknown = remaining.first(where: { Self.isUnknownFlagToken($0, allowedShortFlags: ["-d"]) }) {
throw CLIError(message: """
Expand All @@ -13179,8 +13173,7 @@ struct CMUXCLI {
Known flags:
--workspace <workspace-id>
--window <id|ref|index>
--desktop Base with a VNC screen (the default; first open only)
--base shell-only Base, no screen (alias --no-desktop)
--desktop, --base \(String(localized: "cli.vm.help.legacyBaseKindFlags", defaultValue: "accepted for older scripts; Base always has a screen"))
--focus <true|false> false opens Base without selecting its workspace
--detach, -d
""")
Expand Down Expand Up @@ -13272,7 +13265,7 @@ struct CMUXCLI {
let (targetWorkspaceOpt, rem1) = parseOption(rem0, name: "--workspace")
let (windowOpt, rem2) = parseOption(rem1, name: "--window")
let detach = hasFlag(rem2, name: "--detach") || hasFlag(rem2, name: "-d")
let baseKind = try Self.parseExplicitCloudVMKindFlag(rem2, command: "vm base reset") ?? VMMachineKind.defaultKind
let baseKind = try Self.cloudVMCreateKind(rem2, command: "vm base reset")
let remaining = rem2.filter { !["--detach", "-d", "--desktop", "--base", "--no-desktop"].contains($0) }
if let unknown = remaining.first(where: { Self.isUnknownFlagToken($0, allowedShortFlags: ["-d"]) }) {
throw CLIError(message: """
Expand All @@ -13282,8 +13275,7 @@ struct CMUXCLI {
--reason <text>
--workspace <workspace-id>
--window <id|ref|index>
--desktop Base with a VNC screen (the default)
--base shell-only Base, no screen (alias --no-desktop)
--desktop, --base \(String(localized: "cli.vm.help.legacyBaseKindFlags", defaultValue: "accepted for older scripts; Base always has a screen"))
--detach, -d
""")
}
Expand Down Expand Up @@ -18503,22 +18495,21 @@ struct CMUXCLI {
forwarded ports — each with the address
`vm open` / `surface open` accepts.
status <id> Print provider, status, and image.
base open [--desktop|--base|--no-desktop] [--workspace <id>] [--window <id|ref|index>] [--focus <true|false>] [--detach|-d]
base open [--workspace <id>] [--window <id|ref|index>] [--focus <true|false>] [--detach|-d]
Open Base, your persistent cloud workspace.
Reuses the same VM every time. The first
open creates a desktop (a VNC screen) unless
you pass --base for a shell-only Base.
\(String(localized: "cli.vm.help.baseCreate", defaultValue: "open creates it: the devbox with a VNC screen."))
--focus false opens it without switching
to its workspace.
base reset [--desktop|--base|--no-desktop] [--reason <text>] [--workspace <id>] [--window <id|ref|index>] [--detach|-d]
base reset [--reason <text>] [--workspace <id>] [--window <id|ref|index>] [--detach|-d]
Create a new Base generation. The previous
VM is retained so accidental resets are
recoverable.
new [--desktop|--base] [--size <4g|8g|16g|24g|32g|64g>] [--name <label>] [--provider <provider>] [--window <id|ref|index>] [--focus <true|false>] [--detach|-d]
Create a new machine by kind (a desktop with a
VNC screen by default; --base for shell-only). The server
picks the image for the kind; --image <id>
is an explicit override you normally omit.
new [--size <4g|8g|16g|24g|32g|64g>] [--name <label>] [--provider <provider>] [--window <id|ref|index>] [--focus <true|false>] [--detach|-d]
\(String(localized: "cli.vm.help.newDevbox", defaultValue: "Create a new machine: the devbox with devtools,"))
\(String(localized: "cli.vm.help.newDevboxScreen", defaultValue: "coding agents and a VNC screen. The server picks"))
\(String(localized: "cli.vm.help.newDevboxImage", defaultValue: "the image for the size; --image <id> is an"))
\(String(localized: "cli.vm.help.newDevboxOverride", defaultValue: "explicit override you normally omit."))
--focus false opens the machine without
switching to its workspace (what the New
Machine sheet does).
Expand Down
187 changes: 119 additions & 68 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -1,6 +1,125 @@
{
"sourceLanguage": "en",
"strings": {
"cli.vm.help.baseCreate": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "open creates it: the devbox with a VNC screen."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "初回にVNC画面付きのdevboxを作成します。"
}
}
}
},
"cli.vm.help.legacyBaseKindFlags": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "accepted for older scripts; Base always has a screen"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "旧スクリプトとの互換性のため受け付けます。Baseには常に画面があります"
}
}
}
},
"cli.vm.help.legacyKindFlags": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "accepted for older scripts; every machine has a screen"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "旧スクリプトとの互換性のため受け付けます。すべてのマシンに画面があります"
}
}
}
},
"cli.vm.help.newDevbox": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Create a new machine: the devbox with devtools,"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "開発ツール、コーディングエージェント、VNC画面付きの"
}
}
}
},
"cli.vm.help.newDevboxImage": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "the image for the size; --image <id> is an"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "イメージを選択します。--image <id>で明示的に"
}
}
}
},
"cli.vm.help.newDevboxOverride": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "explicit override you normally omit."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "指定できますが、通常は省略してください。"
}
}
}
},
"cli.vm.help.newDevboxScreen": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "coding agents and a VNC screen. The server picks"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "新しいdevboxを作成します。サーバーがサイズに応じて"
}
}
}
},
"cloudTree.error.invalidPreviewURL": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -288889,40 +289008,6 @@
}
}
},
"machines.new.kind.desktop.summary": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Terminal plus a screen you can watch and control. Best for browsers and GUI apps."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルに加えて、見たり操作したりできる画面。ブラウザや GUI アプリに最適です。"
}
}
}
},
"machines.new.kind.base.summary": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Terminal only. Boots faster and uses less memory."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "ターミナルのみ。起動が速く、メモリ消費が少なくなります。"
}
}
}
},
"machines.new.plan.single": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -289212,40 +289297,6 @@
}
}
},
"machines.new.kind.desktopUnavailable": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "No desktop image is available in this environment, so this machine is terminal only."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "この環境にはデスクトップイメージがないため、このマシンはターミナルのみになります。"
}
}
}
},
"machines.new.kind.label": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Kind"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "種類"
}
}
}
},
"machines.new.size.label": {
"extractionState": "manual",
"localizations": {
Expand Down
1 change: 0 additions & 1 deletion Sources/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8827,7 +8827,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
mode: .base(workspaceID: workspace.id),
plan: MachineSnapshotBuilder.planSnapshot(activeCount: page?.vms.count ?? 0, limits: page?.limits),
memoryOptionsMb: page?.limits?.memoryOptionsMb ?? [],
imageKinds: page?.limits?.imageKinds ?? [],
submit: { [weak self] request in
guard let self else { return false }
return MachineCreateCoordinator.shared.start(request, cancellableLaunch: { [weak self] arguments, progress, completion in
Expand Down
7 changes: 3 additions & 4 deletions Sources/Cloud/MachinesPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -415,14 +415,13 @@ struct MachinesPanelView: View {

/// + on a free plan at its ceiling is the upgrade moment: open the Pro flow
/// instead of launching a create that the backend would only paywall.
/// Otherwise the New Machine sheet collects the kind and size; its Create
/// runs the same `cmux vm new` path the CLI and palette use, and shows up
/// here as a pending row (`viewModel.pendingCreates`), not as panel chrome.
/// Otherwise the New Machine sheet collects the size; its Create runs the
/// same `cmux vm new` path the CLI and palette use, and shows up here as a
/// pending row (`viewModel.pendingCreates`), not as panel chrome.
private func requestNewMachine() {
NewMachineSheetPresenter.shared.presentNewMachine(
plan: viewModel.plan,
memoryOptionsMb: viewModel.memoryOptionsMb,
imageKinds: viewModel.imageKinds,
preferredWindow: NSApp.keyWindow ?? NSApp.mainWindow,
coordinator: viewModel.createCoordinator
)
Expand Down
2 changes: 0 additions & 2 deletions Sources/Cloud/MachinesPanelViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,6 @@ final class MachinesPanelViewModel: ObservableObject {
/// Last plan limits the list returned; the banner countdown re-derives from
/// these on every local recompute without another round trip.
private var lastLimits: VMPlanLimits?
/// The kinds the backend can serve, for the New Machine sheet's kind picker.
var imageKinds: [VMImageKindOption] { lastLimits?.imageKinds ?? [] }
var memoryOptionsMb: [Int] { lastLimits?.memoryOptionsMb ?? [] }
private var authSignOutObserver: NSObjectProtocol?
private var treeChangeObserver: NSObjectProtocol?
Expand Down
Loading
Loading