Add reset capability for computer-use permissions

New `computer.permissionsReset` RPC method allows resetting all
permissions to not-granted state. Includes underlying platform support,
web client integration, and feature interaction tracking.
This commit is contained in:
Jinjing
2026-09-01 16:36:57 -07:00
parent 7873f73d80
commit abb289d11b
7 changed files with 156 additions and 18 deletions
+38 -7
View File
@@ -1,11 +1,16 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { handleMock, openComputerUsePermissionsMock, getComputerUsePermissionStatusMock } =
vi.hoisted(() => ({
handleMock: vi.fn(),
openComputerUsePermissionsMock: vi.fn(),
getComputerUsePermissionStatusMock: vi.fn()
}))
const {
handleMock,
openComputerUsePermissionsMock,
getComputerUsePermissionStatusMock,
resetComputerUsePermissionsMock
} = vi.hoisted(() => ({
handleMock: vi.fn(),
openComputerUsePermissionsMock: vi.fn(),
getComputerUsePermissionStatusMock: vi.fn(),
resetComputerUsePermissionsMock: vi.fn()
}))
vi.mock('electron', () => ({
ipcMain: {
@@ -15,7 +20,8 @@ vi.mock('electron', () => ({
vi.mock('../computer/macos-computer-use-permissions', () => ({
getComputerUsePermissionStatus: getComputerUsePermissionStatusMock,
openComputerUsePermissions: openComputerUsePermissionsMock
openComputerUsePermissions: openComputerUsePermissionsMock,
resetComputerUsePermissions: resetComputerUsePermissionsMock
}))
import { registerComputerUsePermissionHandlers } from './computer-use-permissions'
@@ -25,6 +31,7 @@ describe('registerComputerUsePermissionHandlers', () => {
handleMock.mockReset()
getComputerUsePermissionStatusMock.mockReset()
openComputerUsePermissionsMock.mockReset()
resetComputerUsePermissionsMock.mockReset()
})
it('launches the computer-use helper setup', async () => {
@@ -68,4 +75,28 @@ describe('registerComputerUsePermissionHandlers', () => {
await expect(registration![1]()).resolves.toBe(result)
expect(getComputerUsePermissionStatusMock).toHaveBeenCalledWith()
})
it('resets all computer-use permissions', async () => {
const result = {
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
helperUnavailableReason: null,
bundleId: 'com.stablyai.orca.computer-use',
permissions: [
{ id: 'accessibility', status: 'not-granted' },
{ id: 'screenshots', status: 'not-granted' }
]
}
resetComputerUsePermissionsMock.mockReturnValue(result)
registerComputerUsePermissionHandlers()
const registration = handleMock.mock.calls.find(
([channel]) => channel === 'computerUsePermissions:reset'
)
expect(registration).toBeTruthy()
await expect(registration![1]()).resolves.toBe(result)
expect(resetComputerUsePermissionsMock).toHaveBeenCalledWith()
})
})
@@ -123,6 +123,11 @@ const METHODS = [
params: z.object({}),
handler: () => ({ permissions: [] })
}),
defineMethod({
name: 'computer.permissionsReset',
params: z.object({}),
handler: () => ({ permissions: [] })
}),
defineMethod({
name: 'computer.click',
params: z.object({}),
@@ -162,13 +167,14 @@ describe('RpcDispatcher feature interactions', () => {
await dispatcher.dispatch(makeRequest('computer.permissions'))
await dispatcher.dispatch(makeRequest('computer.permissionsStatus'))
await dispatcher.dispatch(makeRequest('computer.permissionsReset'))
await dispatcher.dispatch(makeRequest('browser.profileImportFromBrowser'))
await dispatcher.dispatch(makeRequest('browser.profileList'))
await dispatcher.dispatch(makeRequest('browser.profileClearDefaultCookies'))
expect(runtime.recordFeatureInteraction).toHaveBeenCalledWith('computer-use-setup')
expect(runtime.recordFeatureInteraction).toHaveBeenCalledWith('cookie-import')
expect(runtime.recordFeatureInteraction).toHaveBeenCalledTimes(2)
expect(runtime.recordFeatureInteraction).toHaveBeenCalledTimes(3)
})
it('does not record failed runtime methods', async () => {
+23 -2
View File
@@ -10,7 +10,8 @@ const computerMocks = vi.hoisted(() => ({
callComputerSidecarSnapshot: vi.fn(),
resetComputerSidecarForTest: vi.fn(),
openComputerUsePermissions: vi.fn(),
getComputerUsePermissionStatus: vi.fn()
getComputerUsePermissionStatus: vi.fn(),
resetComputerUsePermissions: vi.fn()
}))
vi.mock('../../../computer/sidecar-client', () => ({
@@ -24,7 +25,8 @@ vi.mock('../../../computer/sidecar-client', () => ({
vi.mock('../../../computer/macos-computer-use-permissions', () => ({
openComputerUsePermissions: computerMocks.openComputerUsePermissions,
getComputerUsePermissionStatus: computerMocks.getComputerUsePermissionStatus
getComputerUsePermissionStatus: computerMocks.getComputerUsePermissionStatus,
resetComputerUsePermissions: computerMocks.resetComputerUsePermissions
}))
import { COMPUTER_METHODS, resetComputerSessionsForTest } from './computer'
@@ -39,6 +41,7 @@ describe('computer RPC methods', () => {
computerMocks.resetComputerSidecarForTest.mockReset()
computerMocks.openComputerUsePermissions.mockReset()
computerMocks.getComputerUsePermissionStatus.mockReset()
computerMocks.resetComputerUsePermissions.mockReset()
resetComputerSessionsForTest()
computerMocks.resetComputerSidecarForTest.mockClear()
})
@@ -57,6 +60,7 @@ describe('computer RPC methods', () => {
'computer.pasteText',
'computer.performSecondaryAction',
'computer.permissions',
'computer.permissionsReset',
'computer.permissionsStatus',
'computer.pressKey',
'computer.scroll',
@@ -121,6 +125,23 @@ describe('computer RPC methods', () => {
expect(computerMocks.getComputerUsePermissionStatus).toHaveBeenCalledWith()
})
it('resets all computer-use permissions', async () => {
const result = {
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
helperUnavailableReason: null,
bundleId: 'com.stablyai.orca.computer-use',
permissions: [
{ id: 'accessibility', status: 'not-granted' },
{ id: 'screenshots', status: 'not-granted' }
]
}
computerMocks.resetComputerUsePermissions.mockReturnValue(result)
await expect(call('computer.permissionsReset', {})).resolves.toBe(result)
expect(computerMocks.resetComputerUsePermissions).toHaveBeenCalledWith()
})
it('lists windows through the sidecar', async () => {
const result = {
app: { name: 'Finder', bundleId: 'com.apple.finder', pid: 100 },
+9
View File
@@ -61,6 +61,15 @@ export const COMPUTER_METHODS: RpcMethod[] = [
return getComputerUsePermissionStatus()
}
}),
defineMethod({
name: 'computer.permissionsReset',
params: z.object({}),
handler: async () => {
const { resetComputerUsePermissions } =
await import('../../../computer/macos-computer-use-permissions')
return resetComputerUsePermissions()
}
}),
defineMethod({
name: 'computer.listWindows',
params: ListWindows,
@@ -25,7 +25,8 @@ export function getRuntimeFeatureInteractionId(
if (method.startsWith('emulator.')) {
return null
}
if (method === 'computer.permissions') {
// Opening setup and resetting grants are both permission-management flows.
if (method === 'computer.permissions' || method === 'computer.permissionsReset') {
return 'computer-use-setup'
}
if (
@@ -4,6 +4,7 @@ import type {
RefreshAgentsResult
} from '../../../../preload/api-types'
import type {
ComputerUsePermissionResetResult,
ComputerUsePermissionSetupResult,
ComputerUsePermissionStatusResult
} from '../../../../shared/computer-use-permissions-types'
@@ -132,13 +133,20 @@ export function createComputerUsePermissionsApi(): NonNullable<
nextStep: 'Computer-use permissions are managed on the Orca server.'
})),
reset: () =>
Promise.resolve({
platform: getBrowserPlatform(),
helperAppPath: null,
helperUnavailableReason: 'web_client',
bundleId: null,
permissions: []
})
callRuntimeResult<ComputerUsePermissionResetResult>(
'computer.permissionsReset',
{},
15_000
).catch(
() =>
({
platform: getBrowserPlatform(),
helperAppPath: null,
helperUnavailableReason: 'web_client',
bundleId: null,
permissions: []
}) satisfies ComputerUsePermissionResetResult
)
}
}
@@ -0,0 +1,62 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { RuntimeRpcResponse } from '../../../shared/runtime-rpc-envelope'
import {
installBrowserGlobals,
writeStoredRuntimeEnvironment
} from './web-preload-api-test-harness'
describe('web UI computer-use permission reset', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.unstubAllGlobals()
vi.doUnmock('./web-runtime-client')
})
it('proxies permission reset for paired web clients', async () => {
const calls: { method: string; params: unknown }[] = []
vi.doMock('./web-runtime-client', () => ({
WebRuntimeClient: class {
call(method: string, params: unknown): Promise<RuntimeRpcResponse<unknown>> {
calls.push({ method, params })
return Promise.resolve({
id: method,
ok: true,
result:
method === 'computer.permissionsReset'
? {
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
helperUnavailableReason: null,
bundleId: 'com.stablyai.orca.computer-use',
permissions: [
{ id: 'accessibility', status: 'not-granted' },
{ id: 'screenshots', status: 'not-granted' }
]
}
: {},
_meta: { runtimeId: 'runtime-1' }
})
}
close(): void {}
}
}))
const globals = installBrowserGlobals('Linux')
writeStoredRuntimeEnvironment(globals.storage)
const { installWebPreloadApi } = await import('./web-preload-api')
installWebPreloadApi()
await expect(globals.window.api.computerUsePermissions.reset()).resolves.toMatchObject({
bundleId: 'com.stablyai.orca.computer-use',
permissions: [
{ id: 'accessibility', status: 'not-granted' },
{ id: 'screenshots', status: 'not-granted' }
]
})
expect(calls).toEqual([{ method: 'computer.permissionsReset', params: {} }])
})
})