Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d3065fc
feat(library): Best AI Agents for Querying SQL Databases in Plain Eng…
icecrasher321 Oct 15, 2026
bbad1d1
feat(library): 6 Best AI Agent Options for Ecommerce Operations in 20…
icecrasher321 Oct 15, 2026
d48861a
feat(library): 5 Best Discord AI Bot Builders for Community Automatio…
icecrasher321 Oct 15, 2026
30631ad
feat(library): How Do You Build a WhatsApp AI Agent With Sim? (#9080)
icecrasher321 Oct 15, 2026
d6cd3a2
improvement(browser): accept observe on every page action (#9085)
waleedlatif1 Oct 15, 2026
e309463
improvement(changelog): expand release coverage and refine content st…
waleedlatif1 Oct 15, 2026
9411deb
fix(db): throttle autovacuum on the largest tables so vacuum WAL burs…
waleedlatif1 Oct 15, 2026
ae6d45b
fix(mothership): parse each checkpoint control request on its own and…
waleedlatif1 Oct 15, 2026
fda495d
fix(desktop): make computer use act on the app the user sees (#9084)
waleedlatif1 Oct 15, 2026
6a9a79d
improvement(seo): list the v2 API as a catalog item and answer markdo…
waleedlatif1 Oct 15, 2026
e79bd40
fix(mothership): stop live runs when their chat, owner or access is r…
waleedlatif1 Oct 15, 2026
44b824a
fix(mothership): bound every handed tool call by the deadline its fra…
waleedlatif1 Oct 15, 2026
c41323b
fix(billing): re-read the usage gate often once a payer nears its lim…
waleedlatif1 Oct 15, 2026
455c2bd
fix(mothership): start no tool call with less than a minute of its de…
waleedlatif1 Oct 16, 2026
debdd0e
fix(desktop): accept common computer calls and let input reach attach…
waleedlatif1 Oct 16, 2026
f4fd25e
fix(billing): answer an in-flight retry at once, and tell Chat what p…
waleedlatif1 Oct 16, 2026
7fa70e0
fix(content): keep stored provenance consistent (#9097)
icecrasher321 Oct 16, 2026
ee081a5
improvement(seo): lead comparison pages with a direct answer, re-veri…
waleedlatif1 Oct 16, 2026
f73b9db
feat(memory): authorize scoped note access and durable cleanup (#9096)
mzxchandra Oct 16, 2026
ce1f2f5
fix(desktop): count only UI-opening AX actions as delivered on timeou…
waleedlatif1 Oct 16, 2026
b3fa00b
fix(seo): answer Markdown OPTIONS on unknown paths and list every acc…
waleedlatif1 Oct 16, 2026
1469aa2
chore(mothership): sync the computer-use contract copies (#9105)
waleedlatif1 Oct 16, 2026
ebd8efb
fix(memory): read note access from an unlocked snapshot and defer una…
waleedlatif1 Oct 16, 2026
0c6c375
fix(mothership): serialize headless run admission with chat soft dele…
waleedlatif1 Oct 16, 2026
ecc3128
fix(mothership): hold desktop leases, the resume wait and first claim…
waleedlatif1 Oct 16, 2026
f3cb7a1
fix(media): bill a completed fal generation even when a Stop ends its…
waleedlatif1 Oct 16, 2026
e7076a9
fix(scim): push Stop for runs a directory sync ends, after its transa…
waleedlatif1 Oct 16, 2026
e25a6cf
fix(billing): release the reporting prefix lease once its prefix is s…
waleedlatif1 Oct 16, 2026
62f609b
fix(slack): refuse binding a Search bot to a different app or workspa…
waleedlatif1 Oct 16, 2026
88b4f45
fix(file-search): stop requeueing skipped artifacts, and fix public s…
waleedlatif1 Oct 16, 2026
0e0de65
improvement(search): lead an overflowing Jira all-terms match with is…
waleedlatif1 Oct 16, 2026
30fc8e3
fix(mothership): keep a started call's result after Stop, and let a b…
waleedlatif1 Oct 16, 2026
5eb0b33
fix(copilot): load workflow chat history through the windowed chat ro…
waleedlatif1 Oct 16, 2026
f67d024
improvement(search): fall back to Jira summaries holding half the ter…
waleedlatif1 Oct 16, 2026
9d39d7c
docs(library): update byok-multi-model-ai-agent-builder (#9116)
waleedlatif1 Oct 16, 2026
0899d63
docs(library): update n8n-alternatives (#9119)
waleedlatif1 Oct 16, 2026
bc9e138
docs(library): update langgraph-alternatives (#9118)
waleedlatif1 Oct 16, 2026
a53d92b
docs(library): update best-ai-agents-for-regulated-industry-workflows…
waleedlatif1 Oct 16, 2026
7d4a113
docs(library): update best-ai-agent-platforms-for-enterprise-teams-20…
waleedlatif1 Oct 16, 2026
28eaf54
fix(desktop): tighten computer use input guards for text, tabs, menus…
waleedlatif1 Oct 16, 2026
adbbe18
improvement(search): follow Jira summaries holding every term with th…
waleedlatif1 Oct 16, 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
318 changes: 268 additions & 50 deletions apps/desktop/native/computer-use/ComputerUse.swift

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions apps/desktop/native/computer-use/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

Independent native implementation using public Accessibility, Core Graphics and ScreenCaptureKit APIs. Requires macOS 14+. Package `ComputerUse.swift` as `Sim Computer Use.app/Contents/MacOS/SimComputerUse` with this directory's `Info.plist`; sign the nested app with the desktop distribution identity so TCC grants survive upgrades.

Compile each architecture with `swiftc -parse-as-library -O -target arm64-apple-macosx14.0 ComputerUse.swift -o SimComputerUse` (repeat for `x86_64`, combine with `lipo`). No Apple Events, shell execution, private APIs, global event listeners or clipboard access is used. Coordinate mouse input posts public foreground events only after checking the active app, observed window geometry and topmost target point.
Compile each architecture with `swiftc -parse-as-library -O -target arm64-apple-macosx14.0 ComputerUse.swift -o SimComputerUse` (repeat for `x86_64`, combine with `lipo`). No Apple Events, shell execution, private APIs, global event listeners or clipboard access is used. Coordinate mouse input posts public foreground events only after checking the active app, observed window geometry and that Accessibility hit-testing routes the point to the target app.

## Protocol

Persistent newline-delimited JSON over stdin/stdout: `{id,method,params}` returns `{id,result}` or `{id,error:{code,message,dispatchState?}}`. The producer contract is `worker/packages/contracts/src/computer-use.ts` in mothership, copied into the desktop bridge by contract sync. `request_permission` is a local onboarding method, never a model tool; params are `{permission:"accessibility"|"screenCapture"}`. Status/preflight never triggers prompts. Errors go through the protocol, and stdout contains no logs.

The helper observes up to 2000 accessibility nodes (depth 64, queue 4000, traversal deadline 8 seconds, each AX call timeout 250ms). Selected-window and focused-element ancestry are prioritized; menu traversal is deferred and capped at 80 nodes. Electron accessibility is enabled using its documented `AXManualAccessibility` attribute. The helper observes 100 windows and 1000 apps. Requests are bounded to 128 KiB during ingestion. Text/value input is capped at 32000 UTF-16 units. State references are per-process, per-app, expire after 60 seconds, and are consumed by every attempted action. Restarts invalidate all references. Screen captures exclude window shadows, preserve the selected window’s coordinate geometry, and are capped to 1600 pixels on the longest side and 8 MiB encoded. Complete JSON replies are capped at 16 MiB: oversized observations omit screenshots first, then truncate accessibility nodes, while preserving action outcomes. Screenshots are optional and failure is reported in `screenshotError` without discarding accessibility state. macOS 26 uses the screenshot-specific capture API; the macOS 14/15 API path includes only the selected window in a display filter and crops its exact frame. That older path requires the window to fit within one display and reports `capture_geometry_unavailable` otherwise.
The helper observes up to 2000 accessibility nodes (depth 64, queue 4000, traversal deadline 8 seconds, each AX call timeout 250ms). Selected-window and focused-element ancestry are prioritized. The menu bar and menus are read in place, so an open menu's items and their submenus come first; items of closed menus are read last, capped at 80 menu nodes. Electron accessibility is enabled using its documented `AXManualAccessibility` attribute. The helper observes 100 windows and 1000 apps. Requests are bounded to 128 KiB during ingestion. Text/value input is capped at 32000 UTF-16 units. State references are per-process, per-app, expire after 60 seconds, and are consumed by every action that reaches the app; a request that fails before sending anything reports `dispatchState: "not_started"` and keeps them valid. An Accessibility failure counts as sent unless Accessibility rejected the request before the app saw it (unsupported attribute or action, illegal argument). Restarts invalidate all references. Screen captures exclude window shadows, preserve the selected window’s coordinate geometry, and are capped to 1600 pixels on the longest side and 8 MiB encoded. Complete JSON replies are capped at 16 MiB: oversized observations omit screenshots first, then truncate accessibility nodes, while preserving action outcomes. Screenshots are optional and failure is reported in `screenshotError` without discarding accessibility state. macOS 26 uses the screenshot-specific capture API; the macOS 14/15 API path includes only the selected window in a display filter and crops its exact frame. That older path requires the window to fit within one display and reports `capture_geometry_unavailable` otherwise.

Coordinates in action inputs are window-local points; node frames are global screen points. Window bounds must still match the observed snapshot. App PID and launch time must match. Secure fields and descendants are unavailable as action targets; secure values and labels are suppressed. Sim's own apps and System Settings are protected targets. `get_app_state` can open an installed bundle ID without activating it. `list_apps` discovers installed apps under `/Applications`, `/System/Applications`, and `~/Applications` (bounded to 5000 filesystem entries and depth 3), merging running regular apps; installed apps omit `pid`.
Coordinates in action inputs are window-local points; node frames are global screen points. Coordinate input without a `windowId` targets the window the snapshot selected, and an element target uses its own window; `press_key` with an `elementId` focuses that element first. Window bounds must still match the observed snapshot. App PID and launch time must match. Secure fields and descendants are unavailable as action targets; secure values and labels are suppressed. Sim's own apps and System Settings are protected targets. `get_app_state` can open an installed bundle ID without activating it. `list_apps` discovers installed apps under `/Applications`, `/System/Applications`, and `~/Applications` (bounded to 5000 filesystem entries and depth 3), merging running regular apps; installed apps omit `pid`.

AXPress and AXSetValue are direct semantic operations. Keyboard events target the app PID. Cmd+A uses the focused editor's writable accessibility selection range where supported. Coordinate mouse input requires the target app in front; `activate_app` makes that focus change explicit and confirms it. Click/drag/scroll validate the foreground process, unchanged window geometry and topmost window at the point. Some apps ignore background keyboard events. `dispatched:true,verified:false` means only dispatch succeeded; the agent must observe again to establish the outcome. No foreground activation fallback is hidden inside the helper. Native key codes currently assume the US layout for shortcuts; literal text uses Unicode events. Unsupported shortcuts fail explicitly.
AXPress and AXSetValue are direct semantic operations. Keyboard events target the app PID. Cmd+A uses the focused editor's writable accessibility selection range where supported. Coordinate mouse input requires the target app in front; `activate_app` makes that focus change explicit and confirms it. Click/drag/scroll validate the foreground process, unchanged window geometry and the app that receives the point; the topmost window-list entry is not the click target because Notification Center and the Dock keep full-screen click-through windows. A hit on a window's own background counts as that window. Background tabs and minimized windows share bounds with the window in front, so they take no coordinate input. An action that opens UI (AXPress, AXPick, AXShowMenu) and times out while the app shows a sheet, modal or menu (`cannotComplete`) counts as dispatched; any other action that times out fails as uncertain. A menu-item press that leaves the item's own menu open with the app highlighting that item (Calendar on macOS 26) is completed with Return. Sheets, including Go to Folder and out-of-process open and save panels whose contents report no window, count as the window they are attached to. Menu item labels carry their shortcuts (for example ⇧⌘N). Some apps ignore background keyboard events. `dispatched:true,verified:false` means only dispatch succeeded; the agent must observe again to establish the outcome. No foreground activation fallback is hidden inside the helper. Native key codes currently assume the US layout for shortcuts; literal text uses Unicode events. Unsupported shortcuts fail explicitly.

`input_sequence` executes 1–32 key/text steps against one observed editable element. It validates every step before dispatch and checks the same editor, window, and secure-input ancestry between steps. Enter can continue into another step when the same editor and window retain focus. Actual focus loss, Tab, or navigation shortcuts end the sequence before later input. Explicit `activateFirst: true` activates the target app inside the batch before input; it never enables automatic refocusing between steps. Partial execution reports completed steps and an error; callers must observe before retrying. Every mutation is followed by fresh state under the same authorization and queue entry; `observeAfter` optionally requests a screenshot. Observation failure is reported separately so the mutation is never replayed. The model receives a compact, bounded projection of the native snapshot; omitted nodes are reported explicitly.
`type_text` and `input_sequence` target a text control (text fields and areas, terminals, combo boxes, date fields, or anything with an editable text selection), or a container whose focused editor already holds keyboard focus, such as a web composer; keystrokes are sent only while focus is in a text input. `input_sequence` executes 1–32 key/text steps against one such element. It validates every step before dispatch and checks the same editor, window, and secure-input ancestry between steps. Enter can continue into another step when the same editor and window retain focus. Actual focus loss, Tab, or navigation shortcuts end the sequence before later input. Explicit `activateFirst: true` activates the target app inside the batch before input; it never enables automatic refocusing between steps. Partial execution reports completed steps and an error; callers must observe before retrying. Every mutation is followed by fresh state under the same authorization and queue entry; `observeAfter` optionally requests a screenshot. Observation failure is reported separately so the mutation is never replayed. The model receives a compact, bounded projection of the native snapshot; omitted nodes are reported explicitly.

## Cancellation

Expand Down
20 changes: 19 additions & 1 deletion apps/desktop/native/computer-use/tests/live.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ def mutate(method, snapshot, **params):
return state()


def refused(method, snapshot, **params):
try:
call(method, bundleId=bundle, snapshotId=snapshot['snapshotId'], **params)
except AssertionError as error:
return json.loads(str(error).split(': ', 1)[1])
raise AssertionError(method + ' was not refused')


def coordinate(snapshot, node, fx=0.5, fy=0.5):
window = next(w for w in snapshot['windows'] if w['windowId'] == snapshot['windowId'])
return {'windowId': window['windowId'], 'x': node['x'] - window['x'] + node['width'] * fx, 'y': node['y'] - window['y'] + node['height'] * fy}
Expand Down Expand Up @@ -106,10 +114,20 @@ def coordinate(snapshot, node, fx=0.5, fy=0.5):
scroll = find(snapshot, role='AXScrollArea')
snapshot = mutate('scroll', snapshot, **coordinate(snapshot, scroll), deltaX=0, deltaY=180)
assert any((node.get('value', '').startswith('Scroll: ') and node['value'] != 'Scroll: 0') or (node.get('label', '').startswith('Scroll: ') and node['label'] != 'Scroll: 0') for node in snapshot['nodes']), 'Scroll did not move fixture content'
slider = find(snapshot, role='AXSlider')
error = refused('type_text', snapshot, elementId=slider['elementId'], text=' ')
assert error['code'] == 'unsupported_action' and error['dispatchState'] == 'not_started', error
assert find(state(), role='AXSlider')['value'] == slider['value'], 'Text reached a control that does not take text'
snapshot = state()
snapshot = mutate('click', snapshot, elementId=find(snapshot, role='AXTextField', label='Fixture editable text')['elementId'])
snapshot = mutate('type_text', snapshot, elementId=find(snapshot, role='AXWindow')['elementId'], text=' window')
assert find(snapshot, role='AXTextField', label='Fixture editable text')['value'].endswith(' window'), 'Window target did not type into its focused editor'
window = next(w for w in snapshot['windows'] if w['windowId'] == snapshot['windowId'])
snapshot = mutate('click', snapshot, x=window['width'] * 0.75, y=8)
button = find(snapshot, role='AXButton', label='Increment')
snapshot = mutate('perform_action', snapshot, elementId=button['elementId'], accessibilityAction='AXPress')
assert any(f'Count: {baseline + 2}' in node.get('value', '') or f'Count: {baseline + 2}' in node.get('label', '') for node in snapshot['nodes']), 'Explicit AX action did not increment'
print('PASS: real fixture state, secure suppression, AX click/set/action, Unicode typing, keyboard chord, coordinate drag and scrolling; every mutation verified in fresh state; screenshot=' + ('verified' if permissions['screenRecording'] else 'BLOCKED'))
print('PASS: real fixture state, secure suppression, AX click/set/action, Unicode typing, keyboard chord, coordinate drag and scrolling, text refused by a slider, a window target typing into its focused editor, a title-bar click; every mutation verified in fresh state; screenshot=' + ('verified' if permissions['screenRecording'] else 'BLOCKED'))
finally:
if fixture:
fixture.terminate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,25 @@ describe('post-action observation', () => {
})
}
)

it.each([
'browser_click_at',
'browser_insert_text',
'browser_select_option',
'browser_set_checked',
'browser_drag',
] as const)('observes after %s instead of refusing the call', async (tool) => {
await expect(
withPostActionObservation(
tool,
{ observe: {} },
async () => ({ dispatched: true }),
async () => ({ outline: 'After' }),
() => {}
)
).resolves.toMatchObject({
dispatched: true,
observation: { ok: true, result: { outline: 'After' } },
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ import { getErrorMessage } from '@sim/utils/errors'
import { isRecordLike } from '@sim/utils/object'
import { ToolError } from '@/main/browser-agent/errors'

/** Actions that accept observe; models carry it over from click and type to these. */
const OBSERVABLE_ACTIONS: ReadonlySet<BrowserToolName> = new Set([
'browser_click',
'browser_click_at',
'browser_type',
'browser_insert_text',
'browser_press_key',
'browser_fill_form',
'browser_select_option',
'browser_set_checked',
'browser_scroll',
'browser_hover',
'browser_drag',
Comment thread
waleedlatif1 marked this conversation as resolved.
'browser_batch',
])

Expand Down
22 changes: 22 additions & 0 deletions apps/desktop/src/main/computer-use/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,28 @@ describe('native computer use authority and lifecycle', () => {
await expect(click('s1', 'chat', 'repeat')).rejects.toThrow('stale')
})

it('keeps a snapshot usable after an action that sent nothing to the app', async () => {
const { state, native, click } = setup()
await state()
native.request.mockRejectedValueOnce(
new ComputerUseError({
code: 'window_occluded',
message: 'Covered.',
dispatchState: 'not_started',
})
)
await expect(click('s1', 'chat', 'refused')).rejects.toThrow('Covered.')
await expect(click('s1', 'chat', 'retry')).resolves.toMatchObject({ dispatched: true })
})

it('consumes a snapshot after an action whose dispatch is unknown', async () => {
const { state, native, click } = setup()
await state()
native.request.mockRejectedValueOnce(new Error('Computer Use helper exited.'))
await expect(click('s1', 'chat', 'lost')).rejects.toThrow('exited')
await expect(click('s1', 'chat', 'repeat')).rejects.toThrow('stale')
})

it.each(['app', 'window'])('rejects a follow-up snapshot from a different %s', async (target) => {
const { state, service, native, click } = setup()
await state()
Expand Down
Loading
Loading