Skip to content

Commit f525ff2

Browse files
Sg312icecrasher321
andauthored
feat(mothership): add super-user Plan and knowledge graph settings with benchmark tooling (#8637)
* feat(mothership): authorize private cross-workspace memory scopes * feat(desktop): add gated macOS computer use * fix(desktop): preserve generated computer-use contracts * fix(desktop): cancel computer use when its renderer exits * fix(desktop): complete native computer tool lifecycle * fix(desktop): support rich editors and verified Mac input batches * fix: preserve desktop observations through recovery and report app focus * Gate Search Assistant integration lookups with AppConfig (#8301) * Enable read-only integration lookups for Search Assistant * Preserve inherited tool versions in generated integration docs * Align provider tests with Search read-only operations * Gate Search integration tools with AppConfig * Preserve dev test runner isolation during promotion * Default Plan chats to independently saved Opus 5.5 Medium preferences * Remove default Mship execution cutoff and expose Slack agent prerequisites * fix(catalog): preserve credential gates for automation discovery * Add workspace specification benchmarks with isolated Plan evaluation * Add private knowledge graph selection in settings * Retain benchmark run history with comparisons and human review * chore: redeploy Sim after benchmark configuration update * Add superuser benchmark console with selected-user execution * Inspect benchmark workspaces incrementally and search generated specs * Remove benchmark cutoffs and renew active stage leases * Generate benchmark redactions from exact passages * Create owned conversations for every benchmark execution * Preserve partial benchmark answers for human review * Grade benchmark mechanics with verified reference resolution * Reconcile Plan feature with staging contracts and validation gates * Expose the Graphiti rollout gate through authorized memory scope * Share benchmark super-user eligibility with Plan and knowledge graphs * fix(plan): address eligibility, benchmark state and desktop review findings * feat: compare benchmark planner models with saved scores * test: align Plan composer and desktop fixtures with staging * fix(desktop): keep disabled computer use dormant * fix(plan): enforce benchmark and native action admission * fix(plan): preserve secret metadata during benchmark reads * fix(plan): isolate embedded CLI and checkpoint benchmark stages * fix(desktop): bound native requests and completion images * fix(plan): revalidate access and retain benchmark state * fix(plan): qualify persisted effort integration fixtures * fix(db): reconcile benchmark migrations with staging retirement * fix(db): preserve table change log in benchmark migration chain * fix(benchmarks): delete artifacts with their execution target * fix(mothership): isolate benchmark state and preserve tool completions * fix(desktop): preserve action evidence in unload completions * fix(desktop): keep projected parent references valid * fix(computer): preserve uncertainty and label permission controls * test(mothership): await actual tool cancellation completion * fix(plan): resolve review findings and trigger initialization * docs(triggers): point Linq guidance at the builder module * fix(plan): preserve fork scope and benchmark redaction boundaries * fix(plan): retry retained completions and guard benchmark drafts * fix(computer): stop completion delivery at identity reset --------- Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
1 parent 9cc1f0f commit f525ff2

591 files changed

Lines changed: 234942 additions & 1277 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/skills/add-trigger/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export function build{Service}Outputs(): Record<string, TriggerOutput> {
108108

109109
```typescript
110110
import { {Service}Icon } from '@/components/icons'
111-
import { buildTriggerSubBlocks } from '@/triggers'
111+
import { buildTriggerSubBlocks } from '@/triggers/subblocks'
112112
import { build{Service}ExtraFields, build{Service}Outputs, {service}SetupInstructions, {service}TriggerOptions } from '@/triggers/{service}/utils'
113113
import type { TriggerConfig } from '@/triggers/types'
114114

‎apps/desktop/README.md‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,56 @@ Approved native reads can return bounded text, directory listings, images, or PD
224224
## Electron upgrades
225225

226226
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.
227+
228+
229+
## Native Mac computer use
230+
231+
Mothership's `computer` tool is available on macOS 14+ when the global runtime flag
232+
`mothership-computer-use` is enabled and the user opts in under **Settings → Desktop → Computer Use**.
233+
The flag defaults off; local/self-hosted deployments use `MSHIP_COMPUTER_USE=true`, while hosted
234+
rollout uses the existing AppConfig feature-flags document. Older desktop builds omit this optional
235+
bridge and continue to work.
236+
237+
The user grants Accessibility and Screen Recording through macOS, then approves each app for the
238+
current task or persistently. Persistent app approvals are listed in Settings and can be revoked.
239+
Sign-out and deployment changes clear computer-use approval and opt-in. The conversation shows the
240+
active app/action and a Stop button; **⌘⇧Esc** also stops active work when the shortcut is available.
241+
Stop cancels queued actions, in-flight authorization, approval prompts and the native helper.
242+
Already completed input cannot be undone.
243+
244+
The tool lists installed/running apps, reads window accessibility trees, captures selected-window
245+
screenshots, activates apps explicitly, and performs clicks, text input, keyboard shortcuts,
246+
scrolling, dragging, value changes and accessibility actions. Semantic actions can run in the
247+
background. Coordinate mouse actions require explicit foreground activation and a fresh observation;
248+
they validate the target app/window before dispatch. Each mutation consumes its snapshot and the
249+
model observes again to verify results. Secure accessibility values are suppressed. See
250+
[`native/computer-use/README.md`](native/computer-use/README.md) for native bounds and compatibility
251+
limits; screenshots can still contain sensitive content visibly rendered by the approved app.
252+
253+
Every execution is authorized and claimed once by `/api/desktop/computer/authorize` using the
254+
stored tool arguments, current chat access, run/Stop state and current flag. Renderer-supplied
255+
arguments are not execution authority. The main process serializes native work and binds snapshots
256+
to the authorized chat. The Swift helper communicates over private stdio and is packaged outside
257+
ASAR as `Contents/Resources/Sim Computer Use.app`.
258+
259+
Distribution signing must cover the nested helper with the same stable Developer ID identity.
260+
Development ad-hoc builds can lose macOS grants when rebuilt. macOS can attribute permission
261+
requests to the responsible parent app (Sim, Electron, or the terminal/IDE that launched a test),
262+
so confirm the name in the system permission dialog. Test a Finder/LaunchServices-launched signed
263+
build for release acceptance; terminal-launched helper tests do not establish that app's TCC grants.
264+
265+
Validation includes desktop lifecycle/transport unit tests, the Electron authorization/Stop test
266+
in `e2e/computer-use.spec.ts`, the native protocol and live fixture tests, Sim authorization/stream
267+
regressions, and Mothership's opt-in `apps/server/scripts/computer-use-live.ts` real-model trial.
268+
The live trial uses only the disposable fixture and verifies mutations by fresh state; visual mode
269+
also asks the model to identify a drawn shape unavailable in accessibility text.
270+
271+
272+
September 24 acceptance: 1,909 desktop regression tests, the Electron canonical-authorization/
273+
replay/Stop test, native pure/protocol tests, and the live fixture GUI suite passed. A real native
274+
Calculator background test verified `2 + 3 = 5` without changing the foreground app. The signed
275+
arm64 test bundle and nested universal helper passed strict code-signature verification. Its
276+
LaunchServices-launched acceptance remained blocked on that app identity's Accessibility and
277+
Screen Recording grants; the separately granted development helper's tests do not satisfy this
278+
last release-onboarding check. The disposable packaged test follows the existing packaged-smoke
279+
suite's mock-keychain convention; native TCC permissions remain real.
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2+
import { createServer, type Server } from 'node:http'
3+
import { tmpdir } from 'node:os'
4+
import { join } from 'node:path'
5+
import { fileURLToPath } from 'node:url'
6+
import { type ElectronApplication, _electron as electron, expect, test } from '@playwright/test'
7+
import type { SimDesktopApi } from '@sim/desktop-bridge'
8+
9+
const DESKTOP_DIR = fileURLToPath(new URL('..', import.meta.url))
10+
11+
test('computer use reaches the actual helper only with canonical one-shot authorization', async () => {
12+
test.skip(process.platform !== 'darwin', 'The native helper is macOS-only.')
13+
const root = mkdtempSync(join(tmpdir(), 'sim-computer-use-e2e-'))
14+
let server: Server | undefined
15+
let app: ElectronApplication | undefined
16+
let claimed = false
17+
let authorizationCount = 0
18+
let releaseAuthorization: () => void = () => {}
19+
let markAuthorizationStarted: () => void = () => {}
20+
const authorizationStarted = new Promise<void>((resolve) => {
21+
markAuthorizationStarted = resolve
22+
})
23+
const authorizationReleased = new Promise<void>((resolve) => {
24+
releaseAuthorization = resolve
25+
})
26+
try {
27+
server = createServer(async (request, response) => {
28+
const path = new URL(request.url ?? '/', 'http://127.0.0.1').pathname
29+
if (path === '/api/auth/get-session') {
30+
response.writeHead(200, { 'Content-Type': 'application/json' }).end(
31+
JSON.stringify({
32+
user: { id: 'computer-test' },
33+
session: { id: 'computer-test-session' },
34+
})
35+
)
36+
return
37+
}
38+
if (path === '/api/desktop/computer/authorize') {
39+
authorizationCount += 1
40+
let body = ''
41+
for await (const chunk of request) body += chunk.toString()
42+
const input = JSON.parse(body)
43+
expect(Object.keys(input)).toEqual(['toolCallId'])
44+
if (input.toolCallId === 'delayed') {
45+
markAuthorizationStarted()
46+
await authorizationReleased
47+
} else if (input.toolCallId !== 'status-once' || claimed) {
48+
response.writeHead(403, { 'Content-Type': 'application/json' }).end('{}')
49+
return
50+
}
51+
claimed = true
52+
response.writeHead(200, { 'Content-Type': 'application/json' }).end(
53+
JSON.stringify({
54+
chatId: 'computer-chat',
55+
toolName: 'computer',
56+
args: { action: 'status' },
57+
})
58+
)
59+
return
60+
}
61+
response
62+
.writeHead(200, {
63+
'Content-Type': 'text/html',
64+
'Set-Cookie': 'better-auth.session_token=fixture; HttpOnly; SameSite=Lax; Path=/',
65+
})
66+
.end(
67+
`<!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>`
68+
)
69+
})
70+
await new Promise<void>((resolve) => server?.listen(0, '127.0.0.1', resolve))
71+
const address = server.address()
72+
if (!address || typeof address === 'string') throw new Error('Missing local fixture port')
73+
const profile = join(root, 'profile')
74+
mkdirSync(profile)
75+
writeFileSync(
76+
join(profile, 'settings.json'),
77+
JSON.stringify({ origin: `http://127.0.0.1:${address.port}`, computerUseEnabled: true })
78+
)
79+
app = await electron.launch({
80+
args: ['.'],
81+
cwd: DESKTOP_DIR,
82+
env: {
83+
...process.env,
84+
SIM_DESKTOP_ORIGIN: `http://127.0.0.1:${address.port}`,
85+
SIM_DESKTOP_USER_DATA: profile,
86+
},
87+
})
88+
const window = await app.firstWindow()
89+
await expect(window.getByRole('heading')).toHaveText('Computer Use')
90+
const status = await window.evaluate(async () => {
91+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
92+
return api.computerUse?.getStatus()
93+
})
94+
expect(status).toMatchObject({ supported: true, enabled: true, activeAction: null })
95+
const result = await window.evaluate(async () => {
96+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
97+
return api.computerUse?.executeTool('status-once', {
98+
action: 'get_app_state',
99+
bundleId: 'com.apple.systempreferences',
100+
})
101+
})
102+
expect(result).toMatchObject({ kind: 'status', platform: 'darwin' })
103+
const replay = await window.evaluate(async () => {
104+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
105+
try {
106+
await api.computerUse?.executeTool('status-once', { action: 'status' })
107+
return 'unexpected success'
108+
} catch {
109+
return 'rejected'
110+
}
111+
})
112+
expect(replay).toBe('rejected')
113+
expect(authorizationCount).toBe(2)
114+
const delayed = window.evaluate(async () => {
115+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
116+
try {
117+
await api.computerUse?.executeTool('delayed', { action: 'status' })
118+
return 'unexpected success'
119+
} catch {
120+
return 'stopped'
121+
}
122+
})
123+
await authorizationStarted
124+
await window.evaluate(async () => {
125+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
126+
await api.computerUse?.cancel('delayed')
127+
})
128+
releaseAuthorization()
129+
expect(await delayed).toBe('stopped')
130+
expect(authorizationCount).toBe(3)
131+
await window.getByRole('button', { name: 'Disable Computer Use' }).click()
132+
await expect(window.locator('#status')).toHaveText('false')
133+
const disabled = await window.evaluate(async () => {
134+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
135+
try {
136+
await api.computerUse?.executeTool('disabled', { action: 'status' })
137+
return 'unexpected success'
138+
} catch {
139+
return 'rejected'
140+
}
141+
})
142+
expect(disabled).toBe('rejected')
143+
expect(authorizationCount).toBe(3)
144+
} finally {
145+
releaseAuthorization()
146+
await app?.close()
147+
await new Promise<void>((resolve) => (server ? server.close(() => resolve()) : resolve()))
148+
rmSync(root, { recursive: true, force: true })
149+
}
150+
})

‎apps/desktop/e2e/local-files.spec.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,17 @@ import { ToastProvider } from '@sim/emcn';
6363
import { AppRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime';
6464
import { PathParamsContext } from 'next/dist/shared/lib/hooks-client-context.shared-runtime';
6565
import { Desktop } from '@/app/workspace/[workspaceId]/settings/components/desktop/desktop';
66+
import { FeatureFlagsProvider } from '@/app/workspace/[workspaceId]/providers/feature-flags-provider';
6667
import { SettingsHeaderProvider, SettingsHeaderShell } from '@/components/settings/settings-header';
6768
import { SettingsSectionProvider } from '@/components/settings/settings-panel';
6869
const router = { bfcacheId: 'fixture', back: () => history.back(), forward: () => history.forward(), refresh: () => location.reload(), push: url => location.assign(url), replace: url => location.replace(url), prefetch: () => {} };
6970
createRoot(document.getElementById('settings')).render(
7071
<AppRouterContext.Provider value={router}>
7172
<PathParamsContext.Provider value={{ workspaceId: 'fixture' }}>
7273
<ToastProvider><SettingsHeaderProvider><SettingsHeaderShell>
73-
<SettingsSectionProvider plane='workspace' section='desktop'><Desktop /></SettingsSectionProvider>
74+
<FeatureFlagsProvider flags={{ dashboards: false, 'workflow-tests': false, 'mothership-model-selector': false, 'mothership-plan-mode': false }}>
75+
<SettingsSectionProvider plane='workspace' section='desktop'><Desktop /></SettingsSectionProvider>
76+
</FeatureFlagsProvider>
7477
</SettingsHeaderShell></SettingsHeaderProvider></ToastProvider>
7578
</PathParamsContext.Provider>
7679
</AppRouterContext.Provider>

‎apps/desktop/electron-builder.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@ directories:
88

99
files:
1010
- dist/**
11+
- "!dist/native/Sim Computer Use.app/**"
1112
- static/**
1213
- package.json
1314
- from: ../sim/app/_styles/fonts/season
1415
to: static
1516
filter:
1617
- SeasonSansUprightsVF.woff2
1718

19+
extraResources:
20+
- from: dist/native/Sim Computer Use.app
21+
to: Sim Computer Use.app
22+
1823
asar: true
1924

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

0 commit comments

Comments
 (0)