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 });
}
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@

import assert from 'node:assert/strict';
import test from 'node:test';
import { buildComputerUseTools, type ComputerUseToolSet } from '@maka/runtime/computer-use-tools';
import {
buildComputerUseTools,
computerWireParams,
type ComputerUseToolSet,
} from '@maka/runtime/computer-use-tools';
import { type CuDispatchBackend } from '@maka/runtime/computer-use-types';
import { type MakaTool, type MakaToolContext } from '@maka/runtime/tool-runtime';
import type { ClientCapabilityProvider } from '@maka/runtime-host/client';
Expand All @@ -29,12 +33,62 @@ import {
type ClientCapabilityCallFrame,
type ClientCapabilityServiceCallFrame,
} from '@maka/runtime-host/protocol';
import Ajv2020 from 'ajv/dist/2020.js';
import { z } from 'zod';
import { buildClientSettingsTools } from '../client-settings-tools.js';
import { browserOriginAdmission } from '../browser/browser-origin-admission.js';
import { buildRiveWorkflowTool } from '../rive-workflow-tool.js';
import { createDesktopNativeCapabilityProvider } from '../runtime-host-native-capabilities.js';

const COMPUTER_USE_GEOMETRY_SAMPLES = {
valid: [
{
action: 'window_action',
observation_id: 'observation-1',
element_id: '0',
window_action: 'move',
position: [-193, -1049],
},
{
action: 'window_action',
observation_id: 'observation-1',
element_id: '0',
window_action: 'resize',
size: [800, 600],
},
],
invalid: [
{
action: 'window_action',
observation_id: 'observation-1',
element_id: '0',
window_action: 'move',
position: [1, 2, 3],
},
{
action: 'window_action',
observation_id: 'observation-1',
element_id: '0',
window_action: 'move',
position: [1.5, 2],
},
{
action: 'window_action',
observation_id: 'observation-1',
element_id: '0',
window_action: 'resize',
size: [-1, 600],
},
{
action: 'window_action',
observation_id: 'observation-1',
element_id: '0',
window_action: 'resize',
size: [800],
},
],
} as const;

test('publishes self-described session-affine Browser and Computer Use offers', () => {
const provider = createDesktopNativeCapabilityProvider({
browserTools: [tool('browser_snapshot', z.object({ includeHidden: z.boolean().optional() }), async () => 'ok')],
Expand Down Expand Up @@ -127,7 +181,24 @@ test('publishes the real Computer Use schema through the Client Capability proto
offers: provider.offers(),
}),
);
const actionSchema = provider.offers()[0]?.tools[0]?.inputSchema.properties as
const descriptor = provider.offers()[0]?.tools[0];
assert.ok(descriptor);
const ajv = new Ajv2020();
assert.equal(
ajv.validateSchema(descriptor.inputSchema),
true,
JSON.stringify(ajv.errors),
);
const validate = ajv.compile(descriptor.inputSchema);
for (const input of COMPUTER_USE_GEOMETRY_SAMPLES.valid) {
assert.equal(computerWireParams.safeParse(input).success, true);
assert.equal(validate(input), true, JSON.stringify(validate.errors));
}
for (const input of COMPUTER_USE_GEOMETRY_SAMPLES.invalid) {
assert.equal(computerWireParams.safeParse(input).success, false);
assert.equal(validate(input), false, JSON.stringify(input));
}
const actionSchema = descriptor.inputSchema.properties as
| Record<string, { enum?: unknown }>
| undefined;
assert.equal(
Expand Down
Loading
Loading