Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
2a6d70a
feat(mothership): authorize private cross-workspace memory scopes
Sg312 Sep 24, 2026
aa78e1a
feat(desktop): add gated macOS computer use
Sg312 Sep 24, 2026
1eb60c8
fix(desktop): preserve generated computer-use contracts
Sg312 Sep 24, 2026
cd70d4d
fix(desktop): cancel computer use when its renderer exits
Sg312 Sep 24, 2026
e719db9
fix(desktop): complete native computer tool lifecycle
Sg312 Sep 24, 2026
f5730a9
fix(desktop): support rich editors and verified Mac input batches
Sg312 Sep 24, 2026
ee3c2e5
fix: preserve desktop observations through recovery and report app focus
Sg312 Sep 25, 2026
5be85ab
Gate Search Assistant integration lookups with AppConfig (#8301)
Sg312 Sep 25, 2026
8a4edfd
Default Plan chats to independently saved Opus 5.5 Medium preferences
Sg312 Sep 26, 2026
8414809
Remove default Mship execution cutoff and expose Slack agent prerequi…
Sg312 Sep 28, 2026
4e34c1c
fix(catalog): preserve credential gates for automation discovery
Sg312 Sep 28, 2026
bb044a5
Add workspace specification benchmarks with isolated Plan evaluation
icecrasher321 Sep 29, 2026
45e3d9d
Add private knowledge graph selection in settings
Sg312 Sep 29, 2026
c46242c
Retain benchmark run history with comparisons and human review
icecrasher321 Sep 29, 2026
adbb296
chore: redeploy Sim after benchmark configuration update
icecrasher321 Sep 29, 2026
f167c31
Add superuser benchmark console with selected-user execution
icecrasher321 Sep 29, 2026
cf939a0
Inspect benchmark workspaces incrementally and search generated specs
icecrasher321 Sep 29, 2026
4e3b011
Remove benchmark cutoffs and renew active stage leases
icecrasher321 Sep 29, 2026
4b30e08
Generate benchmark redactions from exact passages
icecrasher321 Sep 30, 2026
817aff5
Create owned conversations for every benchmark execution
icecrasher321 Sep 30, 2026
6c59332
Preserve partial benchmark answers for human review
icecrasher321 Sep 30, 2026
f176580
Grade benchmark mechanics with verified reference resolution
icecrasher321 Sep 30, 2026
4599e04
Reconcile Plan feature with staging contracts and validation gates
Sg312 Oct 5, 2026
17c31bc
Expose the Graphiti rollout gate through authorized memory scope
Sg312 Oct 5, 2026
58cbfc5
Share benchmark super-user eligibility with Plan and knowledge graphs
Sg312 Oct 5, 2026
ab98551
fix(plan): address eligibility, benchmark state and desktop review fi…
Sg312 Oct 6, 2026
adf1b68
feat: compare benchmark planner models with saved scores
icecrasher321 Oct 7, 2026
c00dac7
test: align Plan composer and desktop fixtures with staging
icecrasher321 Oct 7, 2026
eaab896
fix(desktop): keep disabled computer use dormant
Sg312 Oct 7, 2026
52a331b
fix(plan): enforce benchmark and native action admission
icecrasher321 Oct 7, 2026
d625c15
fix(plan): preserve secret metadata during benchmark reads
icecrasher321 Oct 8, 2026
80537f9
fix(plan): isolate embedded CLI and checkpoint benchmark stages
icecrasher321 Oct 8, 2026
56deb91
fix(desktop): bound native requests and completion images
icecrasher321 Oct 8, 2026
6639833
fix(plan): revalidate access and retain benchmark state
icecrasher321 Oct 8, 2026
3b2f2bd
fix(plan): qualify persisted effort integration fixtures
icecrasher321 Oct 8, 2026
34ba139
fix(db): reconcile benchmark migrations with staging retirement
icecrasher321 Oct 8, 2026
5ed0d2f
fix(db): preserve table change log in benchmark migration chain
icecrasher321 Oct 8, 2026
f3debd6
fix(benchmarks): delete artifacts with their execution target
Sg312 Oct 8, 2026
066a33a
fix(mothership): isolate benchmark state and preserve tool completions
Sg312 Oct 8, 2026
088574f
fix(desktop): preserve action evidence in unload completions
icecrasher321 Oct 8, 2026
d55dfd6
Merge remote-tracking branch 'origin/staging' into feat/plan-discover…
icecrasher321 Oct 8, 2026
1588740
fix(desktop): keep projected parent references valid
icecrasher321 Oct 9, 2026
6e8138e
Merge remote-tracking branch 'origin/staging' into feat/plan-discover…
Sg312 Oct 9, 2026
1004a02
Merge staging into Plan discovery branch
icecrasher321 Oct 9, 2026
657e1d4
fix(computer): preserve uncertainty and label permission controls
icecrasher321 Oct 9, 2026
dff1309
Merge remote-tracking branch 'origin/feat/plan-discovery-hillclimb' i…
Sg312 Oct 9, 2026
554683d
test(mothership): await actual tool cancellation completion
Sg312 Oct 9, 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
53 changes: 53 additions & 0 deletions apps/desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,56 @@ Raw local file bytes are never exposed through the preload bridge and cannot be
## Electron upgrades

Cadence: Electron ships a major every ~8 weeks and supports the latest 3 — budget a bump every ~4–6 months and adopt security patches within ~2 weeks. Follow `docs/electron-upgrade-checklist.md`; the `desktop-e2e.yml` canary leg (electron@latest) is the early-warning signal.


## Native Mac computer use

Mothership's `computer` tool is available on macOS 14+ when the global runtime flag
`mothership-computer-use` is enabled and the user opts in under **Settings → Desktop → Computer Use**.
The flag defaults off; local/self-hosted deployments use `MSHIP_COMPUTER_USE=true`, while hosted
rollout uses the existing AppConfig feature-flags document. Older desktop builds omit this optional
bridge and continue to work.

The user grants Accessibility and Screen Recording through macOS, then approves each app for the
current task or persistently. Persistent app approvals are listed in Settings and can be revoked.
Sign-out and deployment changes clear computer-use approval and opt-in. The conversation shows the
active app/action and a Stop button; **⌘⇧Esc** also stops active work when the shortcut is available.
Stop cancels queued actions, in-flight authorization, approval prompts and the native helper.
Already completed input cannot be undone.

The tool lists installed/running apps, reads window accessibility trees, captures selected-window
screenshots, activates apps explicitly, and performs clicks, text input, keyboard shortcuts,
scrolling, dragging, value changes and accessibility actions. Semantic actions can run in the
background. Coordinate mouse actions require explicit foreground activation and a fresh observation;
they validate the target app/window before dispatch. Each mutation consumes its snapshot and the
model observes again to verify results. Secure accessibility values are suppressed. See
[`native/computer-use/README.md`](native/computer-use/README.md) for native bounds and compatibility
limits; screenshots can still contain sensitive content visibly rendered by the approved app.

Every execution is authorized and claimed once by `/api/desktop/computer/authorize` using the
stored tool arguments, current chat access, run/Stop state and current flag. Renderer-supplied
arguments are not execution authority. The main process serializes native work and binds snapshots
to the authorized chat. The Swift helper communicates over private stdio and is packaged outside
ASAR as `Contents/Resources/Sim Computer Use.app`.

Distribution signing must cover the nested helper with the same stable Developer ID identity.
Development ad-hoc builds can lose macOS grants when rebuilt. macOS can attribute permission
requests to the responsible parent app (Sim, Electron, or the terminal/IDE that launched a test),
so confirm the name in the system permission dialog. Test a Finder/LaunchServices-launched signed
build for release acceptance; terminal-launched helper tests do not establish that app's TCC grants.

Validation includes desktop lifecycle/transport unit tests, the Electron authorization/Stop test
in `e2e/computer-use.spec.ts`, the native protocol and live fixture tests, Sim authorization/stream
regressions, and Mothership's opt-in `apps/server/scripts/computer-use-live.ts` real-model trial.
The live trial uses only the disposable fixture and verifies mutations by fresh state; visual mode
also asks the model to identify a drawn shape unavailable in accessibility text.


September 24 acceptance: 1,909 desktop regression tests, the Electron canonical-authorization/
replay/Stop test, native pure/protocol tests, and the live fixture GUI suite passed. A real native
Calculator background test verified `2 + 3 = 5` without changing the foreground app. The signed
arm64 test bundle and nested universal helper passed strict code-signature verification. Its
LaunchServices-launched acceptance remained blocked on that app identity's Accessibility and
Screen Recording grants; the separately granted development helper's tests do not satisfy this
last release-onboarding check. The disposable packaged test follows the existing packaged-smoke
suite's mock-keychain convention; native TCC permissions remain real.
150 changes: 150 additions & 0 deletions apps/desktop/e2e/computer-use.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { createServer, type Server } from 'node:http'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { type ElectronApplication, _electron as electron, expect, test } from '@playwright/test'
import type { SimDesktopApi } from '@sim/desktop-bridge'

const DESKTOP_DIR = fileURLToPath(new URL('..', import.meta.url))

test('computer use reaches the actual helper only with canonical one-shot authorization', async () => {
test.skip(process.platform !== 'darwin', 'The native helper is macOS-only.')
const root = mkdtempSync(join(tmpdir(), 'sim-computer-use-e2e-'))
let server: Server | undefined
let app: ElectronApplication | undefined
let claimed = false
let authorizationCount = 0
let releaseAuthorization: () => void = () => {}
let markAuthorizationStarted: () => void = () => {}
const authorizationStarted = new Promise<void>((resolve) => {
markAuthorizationStarted = resolve
})
const authorizationReleased = new Promise<void>((resolve) => {
releaseAuthorization = resolve
})
try {
server = createServer(async (request, response) => {
const path = new URL(request.url ?? '/', 'http://127.0.0.1').pathname
if (path === '/api/auth/get-session') {
response.writeHead(200, { 'Content-Type': 'application/json' }).end(
JSON.stringify({
user: { id: 'computer-test' },
session: { id: 'computer-test-session' },
})
)
return
}
if (path === '/api/desktop/computer/authorize') {
authorizationCount += 1
let body = ''
for await (const chunk of request) body += chunk.toString()
const input = JSON.parse(body)
expect(Object.keys(input)).toEqual(['toolCallId'])
if (input.toolCallId === 'delayed') {
markAuthorizationStarted()
await authorizationReleased
} else if (input.toolCallId !== 'status-once' || claimed) {
response.writeHead(403, { 'Content-Type': 'application/json' }).end('{}')
return
}
claimed = true
response.writeHead(200, { 'Content-Type': 'application/json' }).end(
JSON.stringify({
chatId: 'computer-chat',
toolName: 'computer',
args: { action: 'status' },
})
)
return
}
response
.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': 'better-auth.session_token=fixture; HttpOnly; SameSite=Lax; Path=/',
})
.end(
`<!doctype html><title>Computer Use fixture</title><h1>Computer Use</h1><button id="disable">Disable Computer Use</button><output id="status"></output><script>document.getElementById('disable').onclick=async()=>{const state=await window.simDesktop.computerUse.setEnabled(false);document.getElementById('status').textContent=String(state.enabled)}</script>`
)
})
await new Promise<void>((resolve) => server?.listen(0, '127.0.0.1', resolve))
const address = server.address()
if (!address || typeof address === 'string') throw new Error('Missing local fixture port')
const profile = join(root, 'profile')
mkdirSync(profile)
writeFileSync(
join(profile, 'settings.json'),
JSON.stringify({ origin: `http://127.0.0.1:${address.port}`, computerUseEnabled: true })
)
app = await electron.launch({
args: ['.'],
cwd: DESKTOP_DIR,
env: {
...process.env,
SIM_DESKTOP_ORIGIN: `http://127.0.0.1:${address.port}`,
SIM_DESKTOP_USER_DATA: profile,
},
})
const window = await app.firstWindow()
await expect(window.getByRole('heading')).toHaveText('Computer Use')
const status = await window.evaluate(async () => {
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
return api.computerUse?.getStatus()
})
expect(status).toMatchObject({ supported: true, enabled: true, activeAction: null })
const result = await window.evaluate(async () => {
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
return api.computerUse?.executeTool('status-once', {
action: 'get_app_state',
bundleId: 'com.apple.systempreferences',
})
})
expect(result).toMatchObject({ kind: 'status', platform: 'darwin' })
const replay = await window.evaluate(async () => {
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
try {
await api.computerUse?.executeTool('status-once', { action: 'status' })
return 'unexpected success'
} catch {
return 'rejected'
}
})
expect(replay).toBe('rejected')
expect(authorizationCount).toBe(2)
const delayed = window.evaluate(async () => {
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
try {
await api.computerUse?.executeTool('delayed', { action: 'status' })
return 'unexpected success'
} catch {
return 'stopped'
}
})
await authorizationStarted
await window.evaluate(async () => {
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
await api.computerUse?.cancel('delayed')
})
releaseAuthorization()
expect(await delayed).toBe('stopped')
expect(authorizationCount).toBe(3)
await window.getByRole('button', { name: 'Disable Computer Use' }).click()
await expect(window.locator('#status')).toHaveText('false')
const disabled = await window.evaluate(async () => {
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
try {
await api.computerUse?.executeTool('disabled', { action: 'status' })
return 'unexpected success'
} catch {
return 'rejected'
}
})
expect(disabled).toBe('rejected')
expect(authorizationCount).toBe(3)
} finally {
releaseAuthorization()
await app?.close()
await new Promise<void>((resolve) => (server ? server.close(() => resolve()) : resolve()))
rmSync(root, { recursive: true, force: true })
}
})
6 changes: 6 additions & 0 deletions apps/desktop/electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ directories:

files:
- dist/**
- "!dist/native/Sim Computer Use.app/**"
- static/**
- package.json
- from: ../sim/app/_styles/fonts/season
to: static
filter:
- SeasonSansUprightsVF.woff2

extraResources:
- from: dist/native/Sim Computer Use.app
to: Sim Computer Use.app

asar: true

# Native modules cannot be dlopen'd from inside an asar. The Help-search addon
Expand Down Expand Up @@ -64,6 +69,7 @@ mac:
# macOS refuses to show the microphone prompt at all — it kills the process —
# unless the bundle declares why it wants the device.
extendInfo:
NSScreenCaptureUsageDescription: Mothership captures approved app windows to understand and verify computer-use actions.
NSMicrophoneUsageDescription: Sim uses your microphone for voice input in Chat and for meetings you join in the built-in browser.
NSCameraUsageDescription: Sim uses your camera for meetings you join in the built-in browser, such as Google Meet.
NSBluetoothAlwaysUsageDescription: Sim uses Bluetooth to complete passkey sign-ins with a nearby phone in the built-in browser.
Expand Down
Loading
Loading