Skip to content

Commit 954229b

Browse files
committed
refactor(desktop): drop the terminal and local-file platform gates
Both ship on every platform now, so their feature-table entries, the executor and IPC gates, the optional bridge terminal, and the tests that restated them go away. Also tightens comments the cleanup pass flagged.
1 parent 1f21b02 commit 954229b

24 files changed

Lines changed: 65 additions & 239 deletions

‎apps/desktop/e2e/browser-tools.spec.ts‎

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,22 +1309,18 @@ test.describe('browser tools', () => {
13091309
})
13101310
test('local terminal executes through its native PTY and refuses repeated authorization', async () => {
13111311
await window.evaluate(async (scope) => {
1312-
const terminal = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1313-
.terminal
1314-
if (!terminal) throw new Error('No agent terminal on this platform')
1315-
await terminal.activateScope(scope)
1316-
await terminal.openTerminal(undefined, scope)
1312+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1313+
await api.terminal.activateScope(scope)
1314+
await api.terminal.openTerminal(undefined, scope)
13171315
}, SCOPE)
13181316
calls.set('local-cwd', {
13191317
chatId: SCOPE,
13201318
toolName: 'terminal',
13211319
args: { operation: 'cwd', args: {} },
13221320
})
13231321
const cwd = await window.evaluate(async (scope) => {
1324-
const terminal = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1325-
.terminal
1326-
if (!terminal) throw new Error('No agent terminal on this platform')
1327-
return terminal.executeTool('local-cwd', 'cwd', {}, scope)
1322+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1323+
return api.terminal.executeTool('local-cwd', 'cwd', {}, scope)
13281324
}, SCOPE)
13291325
expect(cwd.ok).toBe(true)
13301326
calls.set('local-run', {
@@ -1333,18 +1329,14 @@ test.describe('browser tools', () => {
13331329
args: { operation: 'run', args: { command: "printf 'SIM_NATIVE_TERMINAL_VERIFIED\\n'" } },
13341330
})
13351331
const result = await window.evaluate(async (scope) => {
1336-
const terminal = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1337-
.terminal
1338-
if (!terminal) throw new Error('No agent terminal on this platform')
1339-
return terminal.executeTool('local-run', 'run', {}, scope)
1332+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1333+
return api.terminal.executeTool('local-run', 'run', {}, scope)
13401334
}, SCOPE)
13411335
expect(result.ok).toBe(true)
13421336
expect(JSON.stringify(result)).toContain('SIM_NATIVE_TERMINAL_VERIFIED')
13431337
const replay = await window.evaluate(async (scope) => {
1344-
const terminal = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1345-
.terminal
1346-
if (!terminal) throw new Error('No agent terminal on this platform')
1347-
return terminal.executeTool('local-run', 'run', {}, scope)
1338+
const api = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi }).simDesktop
1339+
return api.terminal.executeTool('local-run', 'run', {}, scope)
13481340
}, SCOPE)
13491341
expect(replay.ok).toBe(false)
13501342
})

‎apps/desktop/e2e/desktop-tools-live-sim.spec.ts‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -672,13 +672,10 @@ test.describe('desktop tools against a live Sim', () => {
672672
// stays pending and unclaimed. Whether a handed-over command then runs depends on a terminal
673673
// being open for the chat, which this test does not set up, so it checks the claim only.
674674
const response = await page.evaluate(
675-
({ toolCallId, command }) => {
676-
const terminal = (globalThis as DesktopWindow).simDesktop.terminal
677-
if (!terminal) throw new Error('No agent terminal on this platform')
678-
return terminal
675+
({ toolCallId, command }) =>
676+
(globalThis as DesktopWindow).simDesktop.terminal
679677
.executeTool(toolCallId, 'run', { command }, 'unapproved-e2e')
680-
.catch((error: unknown) => ({ ok: false, error: String(error) }))
681-
},
678+
.catch((error: unknown) => ({ ok: false, error: String(error) })),
682679
{ toolCallId: run.toolCallId, command }
683680
)
684681
expect(response).toMatchObject({ ok: false })

‎apps/desktop/e2e/terminal-cancel.spec.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ function bridge(window: Page) {
158158
const terminalId = await window.evaluate(async (scopeId) => {
159159
const terminal = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi })
160160
.simDesktop.terminal
161-
if (!terminal) throw new Error('No agent terminal on this platform')
162161
await terminal.activateScope(scopeId)
163162
terminal.setVisible?.(true, scopeId)
164163
terminal.setFocused(true, scopeId)
@@ -172,7 +171,7 @@ function bridge(window: Page) {
172171
([id, text, scopeId]) =>
173172
(
174173
globalThis as typeof globalThis & { simDesktop: SimDesktopApi }
175-
).simDesktop.terminal?.write(id, text, scopeId),
174+
).simDesktop.terminal.write(id, text, scopeId),
176175
[terminalId, `${command}\r`, scope] as const
177176
)
178177
},
@@ -181,7 +180,6 @@ function bridge(window: Page) {
181180
return window.evaluate(async (scopeId) => {
182181
const terminal = (globalThis as typeof globalThis & { simDesktop: SimDesktopApi })
183182
.simDesktop.terminal
184-
if (!terminal) throw new Error('No agent terminal on this platform')
185183
await terminal.activateScope(scopeId)
186184
return terminal.suspendScope(scopeId)
187185
}, scope)

‎apps/desktop/scripts/build-native.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { createLogger } from '@sim/logger'
55

66
const logger = createLogger('DesktopNativeBuild')
77
if (process.platform === 'win32') {
8-
// Both modules back features the Windows build disables (native local-file
9-
// tools and macOS Help search), so there is nothing to compile.
10-
logger.info('Skipping native desktop modules on Windows: their features are macOS/Linux only')
8+
// Windows opens local files through Node fs (win32-native-files.ts) and has
9+
// no Help search, so neither module is needed.
10+
logger.info('Skipping native desktop modules on Windows: local files use Node fs there')
1111
process.exit(0)
1212
}
1313
if (process.platform !== 'darwin' && process.platform !== 'linux') {

‎apps/desktop/src/main/browser-credentials/os-auth.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@ const AUTH_GRACE_MS = 30_000
2424
* `reveal` hands the plaintext to the Sim renderer; `copy` publishes it to the
2525
* system clipboard, which every process on the machine can read, which
2626
* clipboard managers persist to disk beyond the 30s `clipboard.clear()`, and
27-
* which Universal Clipboard syncs to the user's other devices. (Windows keeps
28-
* the same text in Clipboard History and the cloud clipboard, which is why
29-
* copy is not offered there at all.) Ordering them
27+
* which Universal Clipboard syncs to the user's other devices. Ordering them
3028
* either way lets one consent authorize an exposure the prompt never described.
29+
* Windows does not offer copy at all: Clipboard History keeps the text.
3130
*/
3231
export type SecretOperation = 'reveal' | 'copy'
3332

‎apps/desktop/src/main/desktop-executor/runner.test.ts‎

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ function terminalCall(toolCallId: string, operation: string): ClaimedDesktopCall
3333

3434
function runner(overrides: Partial<DesktopToolRunnerDeps> = {}) {
3535
return createDesktopToolRunner({
36-
platform: 'darwin',
3736
preferences: () => ({ browserEnabled: true, terminalEnabled: true }),
3837
accountDataAvailable: () => true,
3938
browser: {
@@ -140,37 +139,7 @@ describe('background terminal calls', () => {
140139
})
141140
})
142141

143-
describe('terminal calls on Windows', () => {
144-
it('runs them, since the Windows shell ships the terminal', async () => {
145-
const executeTool = vi.fn(async () => ({ ok: true, result: { exitCode: 0 } }))
146-
const completion = await runner({
147-
platform: 'win32',
148-
terminal: { executeTool, cancelTool: vi.fn(async () => true) },
149-
}).run(terminalCall('call-windows', 'run'), new AbortController().signal)
150-
151-
expect(completion.data).not.toMatchObject({ notStarted: true })
152-
expect(executeTool).toHaveBeenCalledTimes(1)
153-
})
154-
})
155-
156142
describe('local file calls', () => {
157-
it.each(['read_local_file', 'import_local_files'])('starts %s on Windows', async (toolName) => {
158-
const request = vi.fn(async () => ({ ok: false as const, error: 'unreadable' }))
159-
await runner({ platform: 'win32', localFiles: { request } }).run(
160-
{
161-
toolCallId: 'local-1',
162-
toolName,
163-
args: { path: '~/notes', targetWorkspaceId: 'ws-1' },
164-
chatId: 'chat-b',
165-
workspaceId: 'ws-1',
166-
executionToken: 'token-local-1',
167-
},
168-
new AbortController().signal
169-
)
170-
171-
expect(request).toHaveBeenCalledOnce()
172-
})
173-
174143
it('names a passing storage state, not a setting, when local files are out of reach', async () => {
175144
const completion = await runner({ accountDataAvailable: () => false }).run(
176145
{

‎apps/desktop/src/main/desktop-executor/runner.ts‎

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ import type {
5252
DesktopImportEntryRequest,
5353
DesktopImportedEntry,
5454
} from '@/main/desktop-executor/protocol'
55-
import { desktopFeatures } from '@/shared/platform-features'
5655

5756
const logger = createLogger('DesktopExecutorRunner')
5857

@@ -79,15 +78,7 @@ function unsupported(toolName: string): DesktopToolCompletion {
7978
return { status: 'error', message, data: { error: message, notStarted: true } }
8079
}
8180

82-
/** The tool needs a surface this platform's shell does not ship, so no update helps. */
83-
function notOnThisPlatform(toolName: string): DesktopToolCompletion {
84-
const message = `Not run: this action never started, because the Sim desktop app cannot run ${toolName} on this operating system. Nothing happened on the user’s computer. Do not retry it; continue without it.`
85-
return { status: 'error', message, data: { error: message, notStarted: true } }
86-
}
87-
8881
export interface DesktopToolRunnerDeps {
89-
/** Selects which {@link desktopFeatures} may run. Defaults to the host. */
90-
platform?: NodeJS.Platform
9182
preferences: () => { browserEnabled: boolean; terminalEnabled: boolean }
9283
/** False while account-bearing local storage is unavailable; local tools then refuse. */
9384
accountDataAvailable: () => boolean
@@ -167,7 +158,6 @@ async function withDeadline<T>(
167158
}
168159

169160
export function createDesktopToolRunner(deps: DesktopToolRunnerDeps): DesktopToolRunner {
170-
const features = desktopFeatures(deps.platform ?? process.platform)
171161
/** Each chat's last terminal operation, until it actually settles. */
172162
const unsettledTerminalWork = new Map<string, Promise<void>>()
173163

@@ -345,15 +335,7 @@ export function createDesktopToolRunner(deps: DesktopToolRunnerDeps): DesktopToo
345335
async run(call, signal) {
346336
try {
347337
if (isCurrentBrowserToolName(call.toolName)) return await runBrowser(call, call.toolName)
348-
if (call.toolName === 'terminal') {
349-
return features.terminal ? await runTerminal(call, signal) : notOnThisPlatform('terminal')
350-
}
351-
if (
352-
!features.localFiles &&
353-
(call.toolName === 'read_local_file' || call.toolName === 'import_local_files')
354-
) {
355-
return notOnThisPlatform(call.toolName)
356-
}
338+
if (call.toolName === 'terminal') return await runTerminal(call, signal)
357339
if (!deps.accountDataAvailable()) return localAccessUnavailable()
358340
if (call.toolName === 'read_local_file') {
359341
return localFileReadCompletion(

‎apps/desktop/src/main/desktop-executor/service.test.ts‎

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,10 @@ function fakeSim(protocolVersion = 1) {
4444
* `'offline'` for a request that never reached Sim.
4545
*/
4646
const registrations: Array<(answer: boolean | number | 'offline') => void> = []
47-
/** The JSON body of each registration request, in order. */
48-
const registrationBodies: unknown[] = []
4947
const fetch = vi.fn(async (url: string, init: RequestInit): Promise<Response> => {
5048
const path = new URL(url).pathname
5149
requests.push(`${init.method} ${path}`)
5250
if (path === '/api/desktop/devices') {
53-
registrationBodies.push(JSON.parse(String(init.body)))
5451
const answer = await new Promise<boolean | number | 'offline'>((resolve) =>
5552
registrations.push(resolve)
5653
)
@@ -96,14 +93,10 @@ function fakeSim(protocolVersion = 1) {
9693
init.signal?.addEventListener('abort', () => reject(new Error('aborted')))
9794
)
9895
})
99-
return { fetch, requests, registrations, registrationBodies, offered }
96+
return { fetch, requests, registrations, offered }
10097
}
10198

102-
async function service(
103-
protocolVersion = 1,
104-
userDataPath?: string,
105-
platform: NodeJS.Platform = 'darwin'
106-
) {
99+
async function service(protocolVersion = 1, userDataPath?: string) {
107100
const sim = fakeSim(protocolVersion)
108101
/** What the sleep blocker was told, in order. */
109102
const busy: boolean[] = []
@@ -124,7 +117,6 @@ async function service(
124117
cancel: async () => {},
125118
},
126119
onBusyChange: (value) => busy.push(value),
127-
platform,
128120
})
129121
return { sim, desktopExecutor, busy }
130122
}
@@ -227,21 +219,6 @@ describe('results recovery will hand to the model', () => {
227219
})
228220

229221
describe('desktop executor registration', () => {
230-
it.each(['darwin', 'win32'] as const)(
231-
'advertises the terminal and native file tools on %s',
232-
async (platform) => {
233-
const { sim, desktopExecutor } = await service(1, undefined, platform)
234-
desktopExecutor.start()
235-
await vi.waitFor(() => expect(sim.registrations).toHaveLength(1))
236-
237-
expect(sim.registrationBodies[0]).toMatchObject({
238-
capabilities: { browser: true, terminal: true, localFiles: true },
239-
})
240-
sim.registrations[0]?.(false)
241-
await desktopExecutor.signOut()
242-
}
243-
)
244-
245222
it('offers the device for binding once Sim enables it', async () => {
246223
const { sim, desktopExecutor } = await service()
247224
desktopExecutor.start()
@@ -334,25 +311,20 @@ describe('desktop executor registration', () => {
334311
}
335312
})
336313

337-
it.skipIf(process.platform === 'win32')(
338-
'registers with no install id it could not save',
339-
async () => {
340-
const userData = await mkdtemp(join(tmpdir(), 'sim-executor-service-'))
341-
await chmod(userData, 0o500)
342-
try {
343-
const { sim, desktopExecutor } = await service(1, userData)
344-
345-
desktopExecutor.start()
346-
await sleep(200)
347-
348-
expect(sim.requests.filter((request) => request.includes('/api/desktop/devices'))).toEqual(
349-
[]
350-
)
351-
} finally {
352-
await chmod(userData, 0o700)
353-
}
314+
it('registers with no install id it could not save', async () => {
315+
const userData = await mkdtemp(join(tmpdir(), 'sim-executor-service-'))
316+
await chmod(userData, 0o500)
317+
try {
318+
const { sim, desktopExecutor } = await service(1, userData)
319+
320+
desktopExecutor.start()
321+
await sleep(200)
322+
323+
expect(sim.requests.filter((request) => request.includes('/api/desktop/devices'))).toEqual([])
324+
} finally {
325+
await chmod(userData, 0o700)
354326
}
355-
)
327+
})
356328

357329
it('keeps its install id through a read failure instead of minting a new one', async () => {
358330
const userData = await mkdtemp(join(tmpdir(), 'sim-executor-service-'))

‎apps/desktop/src/main/desktop-executor/service.ts‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import {
4141
type DesktopImportEntryRequest,
4242
type DesktopImportedEntry,
4343
} from '@/main/desktop-executor/protocol'
44-
import { desktopFeatures } from '@/shared/platform-features'
4544

4645
const logger = createLogger('DesktopExecutorService')
4746

@@ -68,8 +67,6 @@ export interface DesktopExecutorServiceDeps {
6867
onResultDelivered?: (toolCallId: string) => void
6968
/** Sim is done with a call whose real result the model never got. */
7069
onResultNotDelivered?: (toolCallId: string) => void
71-
/** Selects which {@link desktopFeatures} the device advertises. Defaults to the host. */
72-
platform?: NodeJS.Platform
7370
}
7471

7572
export interface DesktopExecutorService {
@@ -323,8 +320,6 @@ export function createDesktopExecutorService(
323320
return
324321
}
325322
const preferences = deps.preferences()
326-
const platform = deps.platform ?? process.platform
327-
const features = desktopFeatures(platform)
328323
const registrationClient = createDesktopExecutorClient({
329324
origin: deps.origin,
330325
fetch: fetchWithAppSession,
@@ -335,12 +330,12 @@ export function createDesktopExecutorService(
335330
deviceId: id,
336331
name: deviceName(),
337332
appVersion: app.getVersion(),
338-
platform: `${platform}-${process.arch}`,
333+
platform: `${process.platform}-${process.arch}`,
339334
capabilities: {
340335
executor: DESKTOP_EXECUTOR_PROTOCOL_VERSION,
341336
browser: preferences.browserEnabled,
342-
terminal: features.terminal && preferences.terminalEnabled,
343-
localFiles: features.localFiles && deps.accountDataAvailable(),
337+
terminal: preferences.terminalEnabled,
338+
localFiles: deps.accountDataAvailable(),
344339
},
345340
})
346341
if (registrationGeneration !== generation || id !== deviceId) return

‎apps/desktop/src/main/ipc.test.ts‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -386,17 +386,12 @@ describe('registerIpcHandlers', () => {
386386
expect(invoke.has(channel) || on.has(channel)).toBe(false)
387387
})
388388

389-
it('keeps the terminal, folder grants, native file tools, and password reveal on Windows', () => {
389+
it('keeps password reveal on Windows while copy and import are withheld', () => {
390390
vi.mocked(ipcMain.handle).mockClear()
391391
vi.mocked(ipcMain.on).mockClear()
392392
registerIpcHandlers({ ...deps, platform: 'win32' })
393393

394-
const { invoke, on } = collectHandlers()
395-
expect(invoke.has('terminal:execute-tool')).toBe(true)
396-
expect(on.has('terminal:write')).toBe(true)
397-
expect(invoke.has('desktop:local-filesystem')).toBe(true)
398-
expect(invoke.has('desktop:local-files')).toBe(true)
399-
expect(invoke.has('browser-credentials:reveal')).toBe(true)
394+
expect(collectHandlers().invoke.has('browser-credentials:reveal')).toBe(true)
400395
})
401396

402397
it('opens validated external URLs only after recent user input', async () => {

0 commit comments

Comments
 (0)