Skip to content

Commit 659e1f7

Browse files
committed
fix(desktop): accept press_key without a window and screenshot shorthand on actions
Mirrors the worker contract: press_key may omit windowId (the key goes to the focused observed window, or a dialog above it), and mutations accept includeScreenshot as shorthand for observeAfter.includeScreenshot.
1 parent 8599bd4 commit 659e1f7

4 files changed

Lines changed: 33 additions & 8 deletions

File tree

‎apps/desktop/native/computer-use/ComputerUse.swift‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,14 +681,20 @@ final class Driver {
681681
}
682682
if request.method == "input_sequence" { return ["kind": "action", "action": request.method, "bundleId": bundle, "dispatched": true, "verified": false, "sequence": ["completedSteps": completed, "totalSteps": steps.count]] }
683683
case "press_key":
684-
let windowId = try required(p.windowId, "windowId"); let frame = try windowRect(windowId, pid: pid)
685-
guard snapshot.windowFrames[windowId] == frame else { throw ComputerError("stale_window", "Observe the target window again.") }
684+
// Without a windowId the key goes to whichever observed window has focus.
685+
let frame = try p.windowId.map { windowId in
686+
let frame = try windowRect(windowId, pid: pid)
687+
guard snapshot.windowFrames[windowId] == frame else { throw ComputerError("stale_window", "Observe the target window again.") }
688+
return frame
689+
}
686690
let root = AXUIElementCreateApplication(pid)
687691
// Keys go to the app's key window: the observed window, or a dialog, panel or popover above it,
688692
// but never another document window the snapshot did not observe.
689693
guard let rawWindow = attribute(root, kAXFocusedWindowAttribute), CFGetTypeID(rawWindow) == AXUIElementGetTypeID() else { throw ComputerError("focus_failed", "The app has no focused window to receive keys; click a window or call activate_app, then observe again.") }
690694
let focusedWindow = unsafeBitCast(rawWindow, to: AXUIElement.self)
691-
guard elementRect(focusedWindow) == frame || attribute(focusedWindow, kAXSubroleAttribute) as? String != kAXStandardWindowSubrole else { throw ComputerError("focus_failed", "Another document window has keyboard focus; observe that window and target it instead.") }
695+
let focusedFrame = elementRect(focusedWindow)
696+
let observed = frame.map { focusedFrame == $0 } ?? snapshot.windowFrames.values.contains { focusedFrame == $0 }
697+
guard observed || attribute(focusedWindow, kAXSubroleAttribute) as? String != kAXStandardWindowSubrole else { throw ComputerError("focus_failed", "Another document window has keyboard focus; observe that window and target it instead.") }
692698
if let focused = attribute(root, kAXFocusedUIElementAttribute), CFGetTypeID(focused) == AXUIElementGetTypeID() { try requireNonSecure(unsafeBitCast(focused, to: AXUIElement.self)) }
693699
let (code, flags) = try parseKey(required(p.key, "key"))
694700
var selectedAll = false

‎apps/desktop/src/main/computer-use/service.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,17 @@ export class ComputerUseService {
235235
? (owner?.elementWindows.get(input.elementId) ?? owner?.windowId)
236236
: undefined
237237
if ('snapshotId' in input) this.snapshots.delete(input.snapshotId)
238-
const params =
239-
'observeAfter' in input ? omit(input, ['action', 'observeAfter']) : omit(input, ['action'])
238+
/** Mutations accept includeScreenshot as shorthand for observeAfter.includeScreenshot. */
239+
const isMutation = input.action !== 'get_app_state'
240+
const observeAfter = {
241+
...('observeAfter' in input ? input.observeAfter : {}),
242+
...(isMutation && 'includeScreenshot' in input && input.includeScreenshot !== undefined
243+
? { includeScreenshot: input.includeScreenshot }
244+
: {}),
245+
}
246+
const params = isMutation
247+
? omit(input, ['action', 'observeAfter', 'includeScreenshot'])
248+
: omit(input, ['action'])
240249
let result: ComputerUseResult
241250
try {
242251
result = await this.deps.native.request(input.action, params)
@@ -264,7 +273,7 @@ export class ComputerUseService {
264273
const observation = await this.deps.native.request('get_app_state', {
265274
bundleId: input.bundleId,
266275
...(windowId ? { windowId } : {}),
267-
...('observeAfter' in input ? input.observeAfter : {}),
276+
...observeAfter,
268277
})
269278
this.check(work)
270279
if (observation.kind !== 'state')

‎apps/sim/lib/mothership/generated/computer-use.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const ObserveAfter = {
2525
.describe(
2626
"Every mutation returns fresh app state by default. Set includeScreenshot to true to include an image; otherwise no screenshot is captured. Observation failure never retries the action.",
2727
),
28+
includeScreenshot: z.boolean().optional().describe("Same as observeAfter.includeScreenshot."),
2829
};
2930
const InputSequenceStep = z.discriminatedUnion("action", [
3031
z.strictObject({ action: z.literal("type_text"), text: z.string().max(32_000) }),
@@ -75,7 +76,11 @@ export const ComputerUseSchema = z.union([
7576
z.strictObject({
7677
action: z.literal("press_key"),
7778
...ObserveAfter,
78-
...WindowTarget,
79+
bundleId: BundleId,
80+
snapshotId: SnapshotId,
81+
windowId: WindowId.optional().describe(
82+
"Window reference from that snapshot. Omit to send the key to the app's focused window.",
83+
),
7984
key: z.string().min(1).max(128).describe("One key or chord, such as Enter, Tab, Escape, or Cmd+A."),
8085
}),
8186
z.strictObject({ action: z.literal("scroll"), ...ElementTarget, ...ObserveAfter, ...ScrollOffsets }),

‎packages/desktop-bridge/src/computer-use.generated.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const ObserveAfter = {
2525
.describe(
2626
"Every mutation returns fresh app state by default. Set includeScreenshot to true to include an image; otherwise no screenshot is captured. Observation failure never retries the action.",
2727
),
28+
includeScreenshot: z.boolean().optional().describe("Same as observeAfter.includeScreenshot."),
2829
};
2930
const InputSequenceStep = z.discriminatedUnion("action", [
3031
z.strictObject({ action: z.literal("type_text"), text: z.string().max(32_000) }),
@@ -75,7 +76,11 @@ export const ComputerUseSchema = z.union([
7576
z.strictObject({
7677
action: z.literal("press_key"),
7778
...ObserveAfter,
78-
...WindowTarget,
79+
bundleId: BundleId,
80+
snapshotId: SnapshotId,
81+
windowId: WindowId.optional().describe(
82+
"Window reference from that snapshot. Omit to send the key to the app's focused window.",
83+
),
7984
key: z.string().min(1).max(128).describe("One key or chord, such as Enter, Tab, Escape, or Cmd+A."),
8085
}),
8186
z.strictObject({ action: z.literal("scroll"), ...ElementTarget, ...ObserveAfter, ...ScrollOffsets }),

0 commit comments

Comments
 (0)