Repository navigation
perf(cloud): create New Machine in process #18700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 10 commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
01a3136
Merge main (85e3c90f441f) into task-cloud-startup-optimization
austinywang a199bfa
test(cloud): pin create attach contract
austinywang 3d4f323
Merge main (31dd5437b1e2) into task-cloud-startup-optimization
austinywang 1b03483
Merge main (31dd5437b1e) into task-cloud-startup-optimization
austinywang 45b4ad1
perf(cloud): create New Machine in process
austinywang a46d8e0
Merge main (6003437cce06) into task-cloud-startup-optimization
austinywang 06bdd63
Merge main (a255d8052d52) into task-cloud-startup-optimization
austinywang a4ee92e
fix(cloud): include token in create attach receipt
austinywang 8bfad3a
fix(cloud): fence cancelled create receipts
austinywang 3df8639
test(cloud): cover upstream errors in create recovery
austinywang 1909b88
fix(cloud): address in-process create review findings
austinywang d8ede06
fix(cloud): fence provider fallback by creation scope
austinywang 86feaa1
fix(cloud): preserve focused pane during reserved adoption
austinywang 63f3d56
fix(cloud): fence in-process workspace adoption
austinywang c4ae416
fix(cloud): repair launcher review regressions
austinywang 995a33a
fix(cloud): admit input before workspace discovery
austinywang 8c4fb07
fix(cloud): reuse detached starter terminal
austinywang 18cf862
Merge origin/main into task-cloud-startup-optimization
austinywang b463a4f
Merge main (42746dd3b8cb) into task-cloud-startup-optimization
austinywang 23e0c32
fix(cloud): preserve create attach session and reservations
austinywang ea44239
Merge main (bbb3633af923) into task-cloud-startup-optimization
austinywang dca4845
test(cloud): exercise session forwarding through bundled CLI
austinywang 8c027cd
fix(cloud): satisfy Swift warning budget
austinywang e1ff481
test(cloud): stabilize hosted UI and CLI fixtures
austinywang 156a179
test(cloud): enable hosted accessibility fixtures
austinywang afad103
Merge remote-tracking branch 'origin/main' into task-cloud-startup-op…
austinywang 0a42df2
test(cloud): use hosted accessibility frames for machine sheet
austinywang 8006181
test(cloud): order hosted pop-ups by visual row
austinywang 3e181b1
test(cloud): keep native machine sheet controls in fixtures
austinywang 28d4e10
test(cloud): locate hosted machine menus by role
austinywang 351b4a3
test(cloud): match hosted machine menus by labels
austinywang 7b972a2
test(cloud): keep accessibility controls in hosted fixtures
austinywang d8c254a
Merge main (f7095c984b95) into task-cloud-startup-optimization
austinywang 7e8efc1
test(cloud): reproduce reconnect and stale discovery regressions
austinywang c2a10d5
fix(cloud): use Cloud session and force post-create discovery
austinywang 40d2ce5
test: stabilize cloud shortcut coverage
austinywang e88bb60
fix: release closed cloud managers and avoid duplicate tmux panes
austinywang 9cdb34e
test: restore shared cloud fixture binding environment
austinywang 2bca6ba
test: avoid waiting for duplicate cloud creation event
austinywang 898412c
fix: preserve optimistic cloud workspace focus
austinywang b97dedb
test: keep delayed cloud reveal visible
austinywang 069bca9
fix(cloud): admit unknown graphs and moved workspaces
austinywang 68c8731
docs(cloud): clarify graph discovery fallback
austinywang 7d28bd9
test(cloud): guard unknown graphs and reserved retry identity
austinywang aba3295
test(cloud): cover pane retries and retain bound identity
austinywang a84f99a
fix(cloud): retain bound identity and retry moved panes safely
austinywang ec4b609
test(cloud): register the destination window for moved-pane retries
austinywang 3b2503c
test(cloud): stabilize moved retry and sheet fixture timing
austinywang 31db0b5
Merge main into task-cloud-startup-optimization
austinywang 4b1f6ef
Merge main (02f7edeeccad) into task-cloud-startup-optimization
austinywang 8641a03
Merge main tip into task-cloud-startup-optimization
austinywang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| import AppKit | ||
| import CmuxCloud | ||
| import CmuxCloudMachines | ||
| import CmuxSurfaceCatalogModel | ||
| import Foundation | ||
|
|
||
| /// Launches the New Machine flow in-process while retaining the CLI-shaped coordinator contract. | ||
| @MainActor | ||
| enum InProcessMachineCreateLauncher { | ||
| struct Invocation: Equatable, Sendable { | ||
| let machineID: String? | ||
| let kind: VMMachineKind? | ||
| let memoryMb: Int? | ||
| let displayName: String? | ||
| let networkPolicy: CloudNetworkPolicy? | ||
| let agentUpdates: CloudAgentUpdates? | ||
| let workspaceID: UUID | ||
| let focus: Bool | ||
| let windowID: UUID? | ||
| } | ||
|
|
||
| nonisolated static let placeholderCommand = "sleep 60" | ||
|
|
||
| /// Recognizes only the New Machine subset. Base, fork, image overrides, and unknown flags stay on CLI. | ||
| nonisolated static func parse(arguments: [String]) -> Invocation? { | ||
| guard arguments.count >= 2, arguments[0] == "vm" else { return nil } | ||
| let verb = arguments[1] | ||
| guard verb == "new" || verb == "open" else { return nil } | ||
| if verb == "open" { | ||
| guard arguments.count >= 3, !arguments[2].isEmpty, !arguments[2].hasPrefix("-") else { return nil } | ||
| var focus = true | ||
| var workspaceID: UUID? | ||
| var windowID: UUID? | ||
| var index = 3 | ||
| while index < arguments.count { | ||
| switch arguments[index] { | ||
| case "--focus": | ||
| guard index + 1 < arguments.count else { return nil } | ||
| switch arguments[index + 1].lowercased() { | ||
| case "true", "1", "yes": focus = true | ||
| case "false", "0", "no": focus = false | ||
| default: return nil | ||
| } | ||
| index += 1 | ||
| case "--workspace": | ||
| guard index + 1 < arguments.count, let value = UUID(uuidString: arguments[index + 1]) else { return nil } | ||
| workspaceID = value; index += 1 | ||
| case "--window": | ||
| guard index + 1 < arguments.count, let value = UUID(uuidString: arguments[index + 1]) else { return nil } | ||
| windowID = value; index += 1 | ||
| default: return nil | ||
| } | ||
| index += 1 | ||
| } | ||
| guard let workspaceID else { return nil } | ||
| return Invocation( | ||
| machineID: arguments[2], kind: nil, memoryMb: nil, displayName: nil, | ||
| networkPolicy: nil, agentUpdates: nil, workspaceID: workspaceID, | ||
| focus: focus, windowID: windowID | ||
| ) | ||
| } | ||
| var kind: VMMachineKind? | ||
| var memoryMb: Int? | ||
| var displayName: String? | ||
| var networkPolicy: CloudNetworkPolicy? | ||
| var agentUpdates: CloudAgentUpdates? | ||
| var focus = true | ||
| var workspaceID: UUID? | ||
| var windowID: UUID? | ||
| var index = 2 | ||
| while index < arguments.count { | ||
| switch arguments[index] { | ||
| case "--desktop": kind = .desktop | ||
| case "--base", "--no-desktop": return nil | ||
| case "--size": | ||
| guard index + 1 < arguments.count, let value = Int(arguments[index + 1]), value > 0 else { return nil } | ||
| memoryMb = value; index += 1 | ||
| case "--name": | ||
| guard index + 1 < arguments.count else { return nil } | ||
| let value = arguments[index + 1].trimmingCharacters(in: .whitespacesAndNewlines) | ||
| displayName = value.isEmpty ? nil : value; index += 1 | ||
| case "--network-policy": | ||
| guard index + 1 < arguments.count, | ||
| let data = arguments[index + 1].data(using: .utf8), | ||
| let object = try? JSONSerialization.jsonObject(with: data), | ||
| let policy = try? CloudNetworkPolicy(foundationObject: object) else { return nil } | ||
| networkPolicy = policy; index += 1 | ||
| case "--agent-updates": | ||
| guard index + 1 < arguments.count, let value = CloudAgentUpdates(rawValue: arguments[index + 1]) else { return nil } | ||
| agentUpdates = value; index += 1 | ||
| case "--focus": | ||
| guard index + 1 < arguments.count else { return nil } | ||
| switch arguments[index + 1].lowercased() { | ||
| case "true", "1", "yes": focus = true | ||
| case "false", "0", "no": focus = false | ||
| default: return nil | ||
| } | ||
| index += 1 | ||
| case "--workspace": | ||
| guard index + 1 < arguments.count, let value = UUID(uuidString: arguments[index + 1]) else { return nil } | ||
| workspaceID = value; index += 1 | ||
| case "--window": | ||
| guard index + 1 < arguments.count, let value = UUID(uuidString: arguments[index + 1]) else { return nil } | ||
| windowID = value; index += 1 | ||
| default: return nil | ||
| } | ||
| index += 1 | ||
| } | ||
| guard let workspaceID else { return nil } | ||
| return Invocation( | ||
| machineID: nil, kind: kind, memoryMb: memoryMb, displayName: displayName, | ||
| networkPolicy: networkPolicy, agentUpdates: agentUpdates, | ||
| workspaceID: workspaceID, focus: focus, windowID: windowID | ||
| ) | ||
| } | ||
|
|
||
| nonisolated static func idempotencyKey(operationID: UUID) -> String { | ||
| "app-" + operationID.uuidString.lowercased() | ||
| } | ||
|
|
||
| struct Dependencies { | ||
| let create: @Sendable (Invocation, String) async throws -> VMSummary | ||
| let status: @Sendable (String) async throws -> VMSummary | ||
| let record: @MainActor (VMSummary, VMCmuxRemoteEndpoint?) async -> Void | ||
| let prepare: @MainActor (UUID) throws -> Void | ||
| let bind: @MainActor (UUID, String) -> Void | ||
| let connect: @MainActor (String) async throws -> Void | ||
| let terminal: @MainActor (String, UUID, Bool) async throws -> String? | ||
| } | ||
|
|
||
| private static func dependencies(client: VMClient, registry: CmuxTuiSurfaceProviderRegistry) -> Dependencies { | ||
| let scope = registry.creationScope | ||
| return Dependencies( | ||
| create: { invocation, key in | ||
| try await client.create( | ||
| kind: invocation.kind, | ||
| persistentHome: false, | ||
| perMachineHome: false, | ||
| memoryMb: invocation.memoryMb, | ||
| displayName: invocation.displayName, | ||
| networkPolicy: invocation.networkPolicy, | ||
| agentUpdates: invocation.agentUpdates, | ||
| idempotencyKey: key | ||
| ) | ||
| }, | ||
| status: { id in try await client.status(id: id) }, | ||
| record: { summary, attach in await registry.recordCreatedMachine(summary, attach: attach, scope: scope) }, | ||
| prepare: { workspaceID in | ||
| guard let workspace = Workspace.liveWorkspace(id: workspaceID), | ||
| workspace.panels.values.contains(where: { $0.panelType == .cloudVMLoading }) else { | ||
| throw CloudMachineLinkManager.ManagerError.retryLater("The reserved Cloud workspace is no longer available.") | ||
| } | ||
| (workspace.panels.values.first { $0.panelType == .cloudVMLoading } as? CloudVMLoadingPanel)?.resetLoading() | ||
| }, | ||
| bind: { workspaceID, machineID in | ||
| SurfaceCatalog.shared.bindCloudWorkspace( | ||
| localWorkspaceID: workspaceID, | ||
| machine: .cloud(machineID), | ||
| remoteWorkspaceID: nil, | ||
| isBase: false, | ||
| generatedTitle: String(localized: "workspace.cloudVM.defaultTitle", defaultValue: "Cloud VM") | ||
| ) | ||
| }, | ||
| connect: { machineID in _ = try await registry.linkSocketPath(machineID: machineID) }, | ||
| terminal: { machineID, workspaceID, focus in | ||
| let payload = try await TerminalController.surfaceNewTerminal( | ||
| machine: .cloud(machineID), command: nil, cwd: nil, name: nil, | ||
| remoteWorkspaceID: nil, | ||
| destination: .workspace(id: workspaceID, placement: .tab), | ||
| focus: focus | ||
| ) | ||
| return payload["remote_workspace_id"] as? String | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| static func run( | ||
| _ invocation: Invocation, | ||
| operationID: UUID, | ||
| dependencies: Dependencies, | ||
| onOutput: @escaping @MainActor (String) -> Void | ||
| ) async -> CloudVMActionLauncher.Completion { | ||
| var output = "" | ||
| var machineID: String? | ||
| do { | ||
| if let openedMachineID = invocation.machineID { | ||
| machineID = openedMachineID | ||
| } else { | ||
| var summary = try await dependencies.create(invocation, idempotencyKey(operationID: operationID)) | ||
| machineID = summary.id | ||
| // A create may finish after the coordinator has tombstoned the | ||
| // operation. Fence cancellation before admitting its receipt so | ||
| // a late completion cannot repopulate another lifecycle. | ||
| try Task.checkCancellation() | ||
| let receipt = "OK machine=\(summary.id)\n" | ||
| output += receipt | ||
| onOutput(receipt) | ||
| if summary.createAttach?.networkAddresses?.ipv4 != nil || summary.createAttach?.networkAddresses?.ipv6 != nil { | ||
| if summary.addressIPv4 == nil { summary.addressIPv4 = summary.createAttach?.networkAddresses?.ipv4 } | ||
| if summary.addressIPv6 == nil { summary.addressIPv6 = summary.createAttach?.networkAddresses?.ipv6 } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| await dependencies.record(summary, summary.createAttach) | ||
| } | ||
| guard let machineID else { throw VMClientError.malformedResponse("Cloud create did not name a machine.") } | ||
| try Task.checkCancellation() | ||
| try dependencies.prepare(invocation.workspaceID) | ||
| dependencies.bind(invocation.workspaceID, machineID) | ||
| try await dependencies.connect(machineID) | ||
| try Task.checkCancellation() | ||
| let remoteWorkspaceID = try await dependencies.terminal(machineID, invocation.workspaceID, invocation.focus) | ||
| if let remoteWorkspaceID, !remoteWorkspaceID.isEmpty { | ||
| SurfaceCatalog.shared.bindCloudWorkspace( | ||
| localWorkspaceID: invocation.workspaceID, | ||
| machine: .cloud(machineID), | ||
| remoteWorkspaceID: remoteWorkspaceID, | ||
| isBase: false, | ||
| generatedTitle: String(localized: "workspace.cloudVM.defaultTitle", defaultValue: "Cloud VM") | ||
| ) | ||
| } | ||
| output += "workspace=\(invocation.workspaceID.uuidString)\n" | ||
| return CloudVMActionLauncher.Completion(terminationStatus: 0, output: output, workspaceId: invocation.workspaceID, machineId: machineID) | ||
| } catch is CancellationError { | ||
| return CloudVMActionLauncher.Completion(terminationStatus: 1, output: output, workspaceId: nil, machineId: machineID, wasCancelled: true) | ||
| } catch { | ||
| return CloudVMActionLauncher.Completion( | ||
| terminationStatus: 1, | ||
| output: output + "\(error)\n", | ||
| workspaceId: nil, | ||
| machineId: machineID | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| @discardableResult | ||
| static func start( | ||
| arguments: [String], | ||
| operationID: UUID, | ||
| onOutput: (@MainActor (String) -> Void)?, | ||
| onCompletion: ((CloudVMActionLauncher.Completion) -> Void)?, | ||
| onCancellationReady: ((CloudVMActionLauncher.CancellationHandle) -> Void)? | ||
| ) -> Bool { | ||
| guard let invocation = parse(arguments: arguments), let client = VMClient.shared else { return false } | ||
| let registry = CmuxTuiSurfaceProviderRegistry.shared | ||
| let deps = dependencies(client: client, registry: registry) | ||
| let task = Task { @MainActor in | ||
| let completion = await run(invocation, operationID: operationID, dependencies: deps, onOutput: { onOutput?($0) }) | ||
| onCompletion?(completion) | ||
| } | ||
| onCancellationReady?(CloudVMActionLauncher.CancellationHandle { task.cancel() }) | ||
| return true | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.