Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
370126e
Add Computer Use command palette onboarding actions
lawrencecchen Sep 4, 2026
c1c623c
Refresh Computer Use helper icon artwork
lawrencecchen Sep 4, 2026
4a6bda7
test(computer-use): reproduce stale permission companion
lawrencecchen Sep 8, 2026
c205b6f
fix(computer-use): track permission companion by target window
lawrencecchen Sep 8, 2026
a07a5da
fix(computer-use): synchronize permission companion tracking
lawrencecchen Sep 8, 2026
181abe6
fix(computer-use): use explicit onboarding entrypoint
lawrencecchen Sep 8, 2026
a7e1c29
fix(computer-use): source helper icon in Icon Composer
lawrencecchen Sep 8, 2026
9a898b3
Merge branch 'codex/computer-use-popover-lifecycle'
lawrencecchen Sep 8, 2026
af9ab5e
test(computer-use): retain onboarding during settings flow
lawrencecchen Sep 8, 2026
2b3f8b1
fix(computer-use): retain onboarding beside settings
lawrencecchen Sep 8, 2026
f14a5dd
Merge remote-tracking branch 'origin/main' into codex/computer-use-po…
lawrencecchen Sep 8, 2026
090f51f
fix(computer-use): remove em dashes from interface copy
lawrencecchen Sep 8, 2026
eb778ab
Merge remote-tracking branch 'origin/main' into codex/computer-use-po…
austinywang Sep 10, 2026
271e664
test(computer-use): reproduce stale companion after app switching
austinywang Sep 10, 2026
9fa4955
fix(computer-use): retain target lifecycle with bounded event sampling
austinywang Sep 10, 2026
9fd59b3
test(computer-use): cover offscreen windows and sampler teardown
austinywang Sep 10, 2026
b2249a0
ci: pin focused macOS tests to SDK 26
austinywang Sep 10, 2026
35cd1f5
fix(computer-use): preserve companion scope and release tracking reso…
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
1 change: 1 addition & 0 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
timeout-minutes: ${{ fromJSON(inputs.job_timeout || '20') }}
env:
TEST_REF: ${{ inputs.ref || github.ref }}
CMUX_CI_REQUIRED_MACOS_SDK_MAJOR: "26"
steps:
- name: Validate Depot runner identity
if: ${{ startsWith((!inputs.runner || inputs.runner == 'auto') && (vars.MACOS_RUNNER_15 || 'blacksmith-6vcpu-macos-15') || inputs.runner, 'depot-macos-') }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public struct CommandPaletteContextKeys: Hashable, Sendable {
public static let authSignedIn = CommandPaletteContextKeys(rawValue: "auth.signedIn")
/// Whether an auth operation is in flight.
public static let authWorking = CommandPaletteContextKeys(rawValue: "auth.working")
/// Whether the Computer Use UX and its onboarding actions are available.
public static let computerUseUXEnabled = CommandPaletteContextKeys(rawValue: "computerUse.uxEnabled")

/// Key for one terminal open-target's availability; `rawValue` is the
/// target's raw identifier (the app layers a typed overload on top).
Expand Down
19 changes: 19 additions & 0 deletions Resources/ComputerUseHelper.icon/Assets/Cursor.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions Resources/ComputerUseHelper.icon/icon.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"fill" : {
"linear-gradient" : [
"extended-srgb:0.19216,0.19216,0.19216,1.00000",
"extended-srgb:0.07843,0.07843,0.07843,1.00000"
]
},
"groups" : [
{
"layers" : [
{
"glass" : false,
"image-name" : "Cursor.svg",
"name" : "Cursor"
}
],
"lighting" : "individual",
"shadow" : {
"kind" : "none",
"opacity" : 0
},
"translucency" : {
"enabled" : false,
"value" : 0
}
}
],
"supported-platforms" : {
"squares" : "shared"
}
}
Binary file modified Resources/ComputerUseHelperIcon.icns
Binary file not shown.
19 changes: 0 additions & 19 deletions Resources/ComputerUseHelperIcon.svg

This file was deleted.

16 changes: 8 additions & 8 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -860,13 +860,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "cmux Computer Use — Running in Background"
"value": "cmux Computer Use: Running in Background"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "cmux Computer Use — バックグラウンドで実行中"
"value": "cmux Computer Use: バックグラウンドで実行中"
}
}
}
Expand Down Expand Up @@ -979,13 +979,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "%1$@ — %2$@"
"value": "%1$@: %2$@"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "%1$@ — %2$@"
"value": "%1$@: %2$@"
}
}
}
Expand Down Expand Up @@ -1404,7 +1404,7 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Drag the app card—not this window—into the permission list."
"value": "Drag the app card, not this window, into the permission list."
}
},
"ja": {
Expand Down Expand Up @@ -1489,13 +1489,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Permissions go to the separate cmux Computer Use helper — the cmux terminal itself never receives them."
"value": "Permissions belong to a separate Computer Use helper. You can quit or reopen it without closing cmux or your terminal sessions."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "権限は独立したcmux Computer Useヘルパーに付与され、cmuxターミナル本体には付与されません。"
"value": "権限は独立したComputer Useヘルパーに付与されます。ヘルパーを終了して再度開いても、cmuxやターミナルセッションは終了しません。"
}
}
}
Expand Down Expand Up @@ -1914,7 +1914,7 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "macOS asks to confirm — allow screen capture"
"value": "macOS asks for confirmation. Allow screen capture."
}
},
"ja": {
Expand Down
167 changes: 21 additions & 146 deletions Sources/App/AgentCursorPointerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private enum ComputerUseCursorArtwork {
if let gradient = CGGradient(
colorsSpace: colorSpace,
colors: colors,
locations: [0.0, 0.5, 1.0]
locations: [0.0, 0.59, 1.0]
) {
context.drawLinearGradient(
gradient,
Expand All @@ -73,159 +73,34 @@ private enum ComputerUseCursorArtwork {
}
}

/// Produces the helper's rounded app icon with the live cursor's exact shape
/// and gradient, drawn on the same tile treatment as the cmux app icon: a
/// vertical plate gradient with a soft top rim highlight (#313131→#141414 in
/// Dark Aqua, #FFFFFF→#ECECEC in Aqua). The plate is rendered explicitly per
/// appearance so the icon always matches the effective cmux appearance (the
/// cmux setting when overridden, the system otherwise).
///
/// Keep the tile constants in sync with
/// `scripts/generate-computer-use-helper-icon.swift`, which bakes the same
/// artwork into `Resources/ComputerUseHelperIcon.icns` for System Settings.
/// Loads the one Icon Composer export used by onboarding and System Settings.
/// Icon Composer owns the mask, plate, rim, and lighting, so every icon surface
/// uses the same rendered asset instead of a parallel AppKit reconstruction.
/// The editable source is `Resources/ComputerUseHelper.icon`; its cursor layer
/// records translation 257.8472/257.8472, scale 45.7900, roundness 16.5, and a
/// 59% gradient midpoint.
@MainActor
enum ComputerUseHelperIconRenderer {
private static let canvasSize = NSSize(width: 1_024, height: 1_024)
private static let plateCornerRadius: CGFloat = 224
private static let cursorTranslation = CGPoint(x: 293.4, y: 293.4)
private static let cursorScale: CGFloat = 44.8
private static let rimWidth: CGFloat = 14
private static var cachedImages: [Bool: NSImage] = [:]

private static func plateGradientColors(dark: Bool) -> [CGColor] {
if dark {
return [
CGColor(gray: 0x31 / 255.0, alpha: 1.0),
CGColor(gray: 0x14 / 255.0, alpha: 1.0),
]
}
return [
CGColor(gray: 1.0, alpha: 1.0),
CGColor(gray: 0xEC / 255.0, alpha: 1.0),
]
}

private static func rimGradientColors(dark: Bool) -> [CGColor] {
if dark {
return [
CGColor(gray: 1.0, alpha: 0.34),
CGColor(gray: 1.0, alpha: 0.05),
]
}
return [
CGColor(gray: 0.0, alpha: 0.10),
CGColor(gray: 0.0, alpha: 0.04),
]
}
private static var cachedImage: NSImage?

static func image(darkMode: Bool? = nil) -> NSImage? {
let isDark = darkMode ?? (
NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
)
if let cached = cachedImages[isDark] {
return cached
// Icon Composer owns appearance, mask, plate, rim, and lighting. Keep
// this parameter for call-site compatibility while the source has one
// shared macOS rendition.
_ = darkMode
if let cachedImage {
return cachedImage
}
guard
let bitmap = NSBitmapImageRep(
bitmapDataPlanes: nil,
pixelsWide: Int(canvasSize.width),
pixelsHigh: Int(canvasSize.height),
bitsPerSample: 8,
samplesPerPixel: 4,
hasAlpha: true,
isPlanar: false,
colorSpaceName: .deviceRGB,
bytesPerRow: 0,
bitsPerPixel: 0
),
let graphicsContext = NSGraphicsContext(bitmapImageRep: bitmap)
else {
guard let url = Bundle.main.url(
forResource: "ComputerUseHelperIcon",
withExtension: "icns"
), let image = NSImage(contentsOf: url) else {
assertionFailure("ComputerUseHelperIcon.icns is missing from the app bundle")
return nil
}
bitmap.size = canvasSize

NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = graphicsContext
defer { NSGraphicsContext.restoreGraphicsState() }

let context = graphicsContext.cgContext
let canvas = CGRect(origin: .zero, size: canvasSize)
context.clear(canvas)
context.setAllowsAntialiasing(true)
context.setShouldAntialias(true)

// Core Graphics is y-up here; flip once so the shared SVG geometry
// keeps the live cursor's up-left direction.
context.saveGState()
context.translateBy(x: 0, y: canvasSize.height)
context.scaleBy(x: 1, y: -1)

let plate = CGPath(
roundedRect: canvas,
cornerWidth: plateCornerRadius,
cornerHeight: plateCornerRadius,
transform: nil
)
let colorSpace = CGColorSpaceCreateDeviceRGB()

// Plate: the app icon's vertical tile gradient. This context is
// flipped, so "top of the icon" is y = 0 here.
context.saveGState()
context.addPath(plate)
context.clip()
if let gradient = CGGradient(
colorsSpace: colorSpace,
colors: plateGradientColors(dark: isDark) as CFArray,
locations: [0.0, 1.0]
) {
context.drawLinearGradient(
gradient,
start: CGPoint(x: canvas.midX, y: 0),
end: CGPoint(x: canvas.midX, y: canvas.height),
options: []
)
}
context.restoreGState()

// Rim: a soft highlight along the tile edge, brightest at the top,
// matching the app icon's inner bevel.
let rim = plate.copy(
strokingWithWidth: rimWidth * 2,
lineCap: .butt,
lineJoin: .miter,
miterLimit: 10
)
context.saveGState()
context.addPath(plate)
context.clip()
context.addPath(rim)
context.clip()
if let gradient = CGGradient(
colorsSpace: colorSpace,
colors: rimGradientColors(dark: isDark) as CFArray,
locations: [0.0, 1.0]
) {
context.drawLinearGradient(
gradient,
start: CGPoint(x: canvas.midX, y: 0),
end: CGPoint(x: canvas.midX, y: canvas.height),
options: []
)
}
context.restoreGState()

context.translateBy(x: cursorTranslation.x, y: cursorTranslation.y)
ComputerUseCursorArtwork.draw(
in: context,
scale: cursorScale
)
context.restoreGState()

let image = NSImage(size: canvasSize)
image.addRepresentation(bitmap)
image.cacheMode = .never
image.isTemplate = false
cachedImages[isDark] = image
image.cacheMode = .never
cachedImage = image
return image
}
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/App/ComputerUseMenuBarController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,13 @@ final class ComputerUseMenuBarController: NSObject, NSMenuDelegate {
} == true
? String(
localized: "computerUse.menu.backgroundStatus",
defaultValue: "cmux Computer Use — Running in Background"
defaultValue: "cmux Computer Use: Running in Background"
)
: String(localized: "computerUse.menu.title", defaultValue: "cmux Computer Use")
let label = activeRow?.targetAppName.map { targetName in
String(
localized: "computerUse.menu.statusWithTarget",
defaultValue: "\(modeLabel) — \(targetName)"
defaultValue: "\(modeLabel): \(targetName)"
)
} ?? modeLabel
statusItem.button?.toolTip = label
Expand Down
Loading
Loading