Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions apps/desktop/bundled-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,21 @@
"hardenedRuntime": false,
"notarization": "missing",
"distributionReady": false
},
"windowsCu": {
"repo": "maka-agent/maka-cu",
"source": "apps/OpenComputerUseWindows/native",
"expectedProtocolVersion": "maka.cu/2",
"binaryName": "maka-cu-windows.exe",
"publishContract": {
"executor": "rust-native-windows",
"protocol": "maka.cu/2",
"runtimeIdentifier": "win-x64",
"rustTarget": "x86_64-pc-windows-msvc",
"cargoProfile": "release",
"lto": true,
"staticNativeDependencies": true
},
"distributionReady": false
}
}
20 changes: 19 additions & 1 deletion apps/desktop/electron-builder.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* under the License.
*/

import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import {
Expand All @@ -31,6 +31,23 @@ function readManifest(relativePath) {
return JSON.parse(readFileSync(new URL(relativePath, import.meta.url), 'utf8'));
}

export function windowsCuExtraResources({
platform = process.platform,
manifest = readManifest('./bundled-tools.json'),
helperExists = existsSync('resources/bin/maka-cu-windows/maka-cu-windows.exe'),
} = {}) {
if (platform !== 'win32' || manifest.windowsCu?.distributionReady !== true) return [];
if (!helperExists) {
throw new Error(
'windowsCu is distribution-ready but resources/bin/maka-cu-windows/maka-cu-windows.exe is missing',
);
}
return [{
from: 'resources/bin/maka-cu-windows',
to: 'bin/maka-cu-windows',
}];
}

// Some license files below ship inside third-party packages that apps/desktop
// depends on (electron, @fontsource-variable/geist*). Locate each package by
// resolving its manifest rather than assuming its node_modules location:
Expand Down Expand Up @@ -143,6 +160,7 @@ const baseDesktopBuilderConfig = {
},
...(process.platform === 'win32'
? [
...windowsCuExtraResources(),
{
from: 'resources/windows-sandbox/maka-windows-sandbox.exe',
to: 'windows-sandbox/maka-windows-sandbox.exe',
Expand Down
131 changes: 130 additions & 1 deletion apps/desktop/src/main/__tests__/computer-use-host.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { chmod, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
import { chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { describe, it } from 'node:test';
Expand Down Expand Up @@ -141,4 +141,133 @@ describe('Computer Use host health', () => {
}
});

it('selects the shared maka.cu/2 backend for a pinned Windows helper', async () => {
const directory = await mkdtemp(join(tmpdir(), 'maka-cu-host-windows-'));
const hosts: Array<ReturnType<typeof createComputerUseHost>> = [];
try {
const helperDirectory = join(directory, 'bin', 'maka-cu-windows');
const binaryPath = join(helperDirectory, 'maka-cu-windows.exe');
const manifestPath = join(directory, 'bundled-tools.json');
const bytes = Buffer.from('windows-native-release-artifact');
await mkdir(helperDirectory, { recursive: true });
await writeFile(binaryPath, bytes);
await chmod(binaryPath, 0o755);
const hash = createHash('sha256').update(bytes).digest('hex');
await writeFile(manifestPath, JSON.stringify({
windowsCu: {
binarySha256: hash,
files: [{ name: 'maka-cu-windows.exe', sizeBytes: bytes.length, sha256: hash }],
distributionReady: false,
},
}));

const validForDevelopment = createComputerUseHost({
isPackaged: false,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(validForDevelopment);
assert.equal(validForDevelopment.selected.backendId, 'maka-cu');

const blockedForDistribution = createComputerUseHost({
isPackaged: true,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(blockedForDistribution);
assert.equal(blockedForDistribution.selected.backendId, 'none');

await writeFile(manifestPath, JSON.stringify({
windowsCu: {
binarySha256: hash,
files: [{ name: 'maka-cu-windows.exe', sizeBytes: bytes.length, sha256: hash }],
distributionReady: true,
},
}));
const selected = createComputerUseHost({
isPackaged: true,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(selected);
assert.equal(selected.selected.backendId, 'maka-cu');

const tamperedBytes = Buffer.from(bytes);
tamperedBytes[0] ^= 0xff;
await writeFile(binaryPath, tamperedBytes);
const withTamperedFile = createComputerUseHost({
isPackaged: true,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(withTamperedFile);
assert.equal(withTamperedFile.selected.backendId, 'none');

await rm(binaryPath);
const withMissingFile = createComputerUseHost({
isPackaged: true,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(withMissingFile);
assert.equal(withMissingFile.selected.backendId, 'none');

await writeFile(binaryPath, bytes);
await chmod(binaryPath, 0o755);
const restored = createComputerUseHost({
isPackaged: true,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(restored);
assert.equal(restored.selected.backendId, 'maka-cu');

await writeFile(join(helperDirectory, 'unexpected.dll'), Buffer.from('unexpected'));
const withUnexpectedFile = createComputerUseHost({
isPackaged: true,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(withUnexpectedFile);
assert.equal(withUnexpectedFile.selected.backendId, 'none');

await rm(join(helperDirectory, 'unexpected.dll'));
await mkdir(join(helperDirectory, 'unexpected-directory'));
const withUnexpectedDirectory = createComputerUseHost({
isPackaged: true,
resourcesPath: directory,
manifestPath,
binaryPath,
platform: 'win32',
physicalInputRecentlyActive: () => false,
});
hosts.push(withUnexpectedDirectory);
assert.equal(withUnexpectedDirectory.selected.backendId, 'none');
} finally {
for (const host of hosts) host.selected.backend?.dispose?.();
await rm(directory, { recursive: true, force: true });
}
});

});
111 changes: 89 additions & 22 deletions apps/desktop/src/main/computer-use-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
fstatSync,
openSync,
readFileSync,
readdirSync,
} from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand All @@ -42,6 +43,15 @@ export interface ComputerUseHostState {
expectedBinarySha256?: string;
}

type BundledToolManifest = {
makaCu?: { binarySha256?: string; distributionReady?: boolean };
windowsCu?: {
binarySha256?: string;
distributionReady?: boolean;
files?: Array<{ name?: string; sizeBytes?: number; sha256?: string }>;
};
};

function readRegularFile(path: string): Buffer {
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
try {
Expand All @@ -54,6 +64,47 @@ function readRegularFile(path: string): Buffer {
}
}

function hasPinnedWindowsHelperFiles(
binaryPath: string,
files: NonNullable<BundledToolManifest['windowsCu']>['files'],
): boolean {
if (!Array.isArray(files) || files.length === 0) return false;
const expected = new Map<string, { sizeBytes: number; sha256: string }>();
for (const file of files) {
if (
typeof file?.name !== 'string' ||
file.name.length === 0 ||
file.name !== file.name.split(/[\\/]/).pop() ||
typeof file.sizeBytes !== 'number' ||
!Number.isSafeInteger(file.sizeBytes) ||
file.sizeBytes < 0 ||
typeof file.sha256 !== 'string' ||
!/^[a-f0-9]{64}$/.test(file.sha256) ||
expected.has(file.name)
) return false;
expected.set(file.name, { sizeBytes: file.sizeBytes, sha256: file.sha256 });
}
let actual: string[];
try {
const entries = readdirSync(dirname(binaryPath), { withFileTypes: true });
if (entries.some((entry) => !entry.isFile())) return false;
actual = entries.map((entry) => entry.name);
} catch {
return false;
}
if (actual.length !== expected.size || actual.some((name) => !expected.has(name))) return false;
for (const [name, pin] of expected) {
try {
const bytes = readRegularFile(join(dirname(binaryPath), name));
if (bytes.byteLength !== pin.sizeBytes) return false;
if (createHash('sha256').update(bytes).digest('hex') !== pin.sha256) return false;
} catch {
return false;
}
}
return expected.has(binaryPath.split(/[\\/]/).pop() ?? '');
}

export function createComputerUseHost(input: {
isPackaged: boolean;
resourcesPath: string;
Expand All @@ -68,6 +119,8 @@ export function createComputerUseHost(input: {
screenLocked?: (context: { sessionId: string }) => boolean | Promise<boolean>;
onTrace?: MakaCuBackendOptions['onTrace'];
overlay?: CuOverlayHook;
/** Test seam for Windows manifest selection. */
platform?: NodeJS.Platform;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
? join(input.resourcesPath, 'bundled-tools.json')
Expand All @@ -77,36 +130,49 @@ export function createComputerUseHost(input: {
'..',
'bundled-tools.json',
));
const binaryPath = input.binaryPath ?? (input.isPackaged
? join(input.resourcesPath, 'bin', 'maka-cu')
: resolve(
dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'resources',
'bin',
'maka-cu',
));
const platform = input.platform ?? process.platform;
const windows = platform === 'win32';
const binaryPath = input.binaryPath ?? (windows
? (process.env.MAKA_WINDOWS_CU_HELPER_PATH ?? (input.isPackaged
? join(input.resourcesPath, 'bin', 'maka-cu-windows', 'maka-cu-windows.exe')
: resolve(
dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'resources',
'bin',
'maka-cu-windows',
'maka-cu-windows.exe',
)))
: (input.isPackaged
? join(input.resourcesPath, 'bin', 'maka-cu')
: resolve(
dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'resources',
'bin',
'maka-cu',
)));
try {
const manifest = JSON.parse(readRegularFile(manifestPath).toString('utf8')) as {
makaCu?: {
binarySha256?: string;
distributionReady?: boolean;
};
};
const expectedBinarySha256 = manifest.makaCu?.binarySha256;
if (input.isPackaged && manifest.makaCu?.distributionReady !== true) {
return { selected: selectComputerUseBackend() };
const manifest = JSON.parse(readRegularFile(manifestPath).toString('utf8')) as BundledToolManifest;
const entry = windows ? manifest.windowsCu : manifest.makaCu;
const expectedBinarySha256 = entry?.binarySha256;
if (input.isPackaged && entry?.distributionReady !== true) {
return { selected: selectComputerUseBackend({ platform }) };
}
if (!expectedBinarySha256 || !/^[a-f0-9]{64}$/.test(expectedBinarySha256)) {
return { selected: selectComputerUseBackend() };
return { selected: selectComputerUseBackend({ platform }) };
}
if (windows && !hasPinnedWindowsHelperFiles(binaryPath, manifest.windowsCu?.files)) {
return { selected: selectComputerUseBackend({ platform }) };
}
accessSync(binaryPath, constants.R_OK | constants.X_OK);
const actual = createHash('sha256')
.update(readRegularFile(binaryPath))
.digest('hex');
if (actual !== expectedBinarySha256) {
return { selected: selectComputerUseBackend() };
return { selected: selectComputerUseBackend({ platform }) };
}
return {
// No `backendId`: the host takes whatever `DEFAULT_CU_BACKEND_ID` names,
Expand All @@ -120,12 +186,13 @@ export function createComputerUseHost(input: {
...(input.screenLocked ? { screenLocked: input.screenLocked } : {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
platform,
}),
binaryPath,
expectedBinarySha256,
};
} catch {
return { selected: selectComputerUseBackend() };
return { selected: selectComputerUseBackend({ platform }) };
}
}

Expand Down
Loading