Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from '../computer-use-real-model-policy.js';

function tool(calls: string[]): MakaTool {
return {
name: 'maka_computer',
description: 'test',
parameters: {},
impl: async (args) => {
calls.push((args as { action: string }).action);
return { text: 'ok' };
},
};
}

function toolSet(calls: string[]): ComputerUseToolSet {
return Object.assign([tool(calls)], {
clearSession(_sessionId: string) {},
sessionEvents: {
snapshot: () => ({ status: 'unobserved' as const, generation: 0 }),
physicalUserIntervened: () => ({ status: 'intervention_debounce' as const, generation: 1 }),
interventionDebounceElapsed: () => ({ status: 'reobserve_required' as const, generation: 1 }),
reobserveRequired: () => ({ status: 'reobserve_required' as const, generation: 1 }),
screenLocked: () => ({ status: 'screen_locked' as const, generation: 1 }),
screenUnlocked: () => ({ status: 'reobserve_required' as const, generation: 1 }),
blockedUrlDetected: () => ({ status: 'blocked_url' as const, generation: 1 }),
userStopped: () => ({ status: 'user_stopped' as const, generation: 1 }),
dynamicContentChanged: () => ({ status: 'unobserved' as const, generation: 0 }),
},
});
}

test('parses one bounded allowlist and rejects malformed policies', () => {
assert.deepEqual(parseComputerUseRealModelPolicy(JSON.stringify({
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
})), {
allowedActions: ['list_apps', 'observe', 'wait'],
maxTotalActions: 4,
});
assert.throws(
() => parseComputerUseRealModelPolicy('{"allowedActions":[],"maxTotalActions":0}'),
/Invalid Computer Use real-model/,
);
});

test('blocks disallowed and over-budget actions before dispatch', async () => {
const calls: string[] = [];
const [wrapped] = applyComputerUseRealModelPolicy(toolSet(calls), {
allowedActions: ['observe'],
maxTotalActions: 2,
});
const context = {
sessionId: 's',
turnId: 't',
toolCallId: 'c',
cwd: '/tmp',
abortSignal: new AbortController().signal,
emitOutput() {},
};

const allowed = await wrapped.impl({ action: 'observe' } as never, context) as { text: string };
assert.equal(allowed.text, 'ok');
const disallowed = await wrapped.impl(
{ action: 'left_click' } as never,
context,
) as { text: string };
assert.match(
disallowed.text,
/unsupported_action_policy/,
);
const overBudget = await wrapped.impl(
{ action: 'observe' } as never,
context,
) as { text: string };
assert.match(
overBudget.text,
/total_action_budget_exceeded/,
);
assert.deepEqual(calls, ['observe']);
});
3 changes: 3 additions & 0 deletions apps/desktop/src/main/computer-use-host.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ import {
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { CuaDriverRoleSnapshot } from '@maka/computer-use';
import type { CuaDriverBackendOptions } from '@maka/computer-use';
import {
selectComputerUseBackend,
type SelectedComputerUseBackend,
Expand DownExpand Up@@ -44,6 +45,7 @@ export function createComputerUseHost(input: {
mimeType: string,
) => { base64: string; mimeType: 'image/png' | 'image/jpeg' };
physicalInputRecentlyActive?: () => boolean | Promise<boolean>;
onTrace?: CuaDriverBackendOptions['onTrace'];
overlay?: CuOverlayHook;
}): ComputerUseHostState {
const manifestPath = input.manifestPath ?? (input.isPackaged
Expand DownExpand Up@@ -101,6 +103,7 @@ export function createComputerUseHost(input: {
...(input.physicalInputRecentlyActive
? { physicalInputRecentlyActive: input.physicalInputRecentlyActive }
: {}),
...(input.onTrace ? { onTrace: input.onTrace } : {}),
...(input.overlay ? { overlay: input.overlay } : {}),
}),
binaryPath,
Expand Down
73 changes: 73 additions & 0 deletions apps/desktop/src/main/computer-use-real-model-policy.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
import type { ComputerUseToolSet, MakaTool } from '@maka/runtime';

export interface ComputerUseRealModelPolicy {
allowedActions: readonly string[];
maxTotalActions: number;
}

export function parseComputerUseRealModelPolicy(
raw: string | undefined,
): ComputerUseRealModelPolicy | undefined {
if (!raw) return undefined;
const value = JSON.parse(raw) as {
allowedActions?: unknown;
maxTotalActions?: unknown;
};
if (
!Array.isArray(value.allowedActions)
|| value.allowedActions.length === 0
|| value.allowedActions.some((action) =>
typeof action !== 'string' || !action.trim())
|| new Set(value.allowedActions).size !== value.allowedActions.length
) {
throw new Error('Invalid Computer Use real-model allowedActions');
}
if (
!Number.isInteger(value.maxTotalActions)
|| (value.maxTotalActions as number) < 1
|| (value.maxTotalActions as number) > 100
) {
throw new Error('Invalid Computer Use real-model maxTotalActions');
}
return {
allowedActions: value.allowedActions,
maxTotalActions: value.maxTotalActions as number,
};
}

export function applyComputerUseRealModelPolicy(
tools: ComputerUseToolSet,
policy: ComputerUseRealModelPolicy | undefined,
): ComputerUseToolSet {
if (!policy) return tools;
let totalActions = 0;
const allowed = new Set(policy.allowedActions);
const wrapped = tools.map((tool) => {
if (tool.name !== 'maka_computer') return tool;
return {
...tool,
impl: async (args, context) => {
const action = typeof (args as { action?: unknown })?.action === 'string'
? (args as { action: string }).action
: 'unknown';
totalActions += 1;
if (totalActions > policy.maxTotalActions) {
return {
text: 'maka_computer failed: total_action_budget_exceeded',
error: 'total_action_budget_exceeded',
};
}
if (!allowed.has(action)) {
return {
text: `maka_computer.${action} failed: unsupported_action_policy`,
error: 'unsupported_action_policy',
};
}
return tool.impl(args as never, context);
},
};
}) as ComputerUseToolSet;
wrapped.clearSession = (sessionId) => tools.clearSession(sessionId);
wrapped.sessionEvents = tools.sessionEvents;
return wrapped;
}
51 changes: 43 additions & 8 deletions apps/desktop/src/main/main.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -173,6 +173,10 @@ import {
createComputerUseHost,
} from './computer-use-host.js';
import { createCursorOverlayController } from './computer-use/cursor-overlay-window.js';
import {
applyComputerUseRealModelPolicy,
parseComputerUseRealModelPolicy,
} from './computer-use-real-model-policy.js';
import { createComputerUseOverlayHook } from '@maka/computer-use';
import { releaseBrowserSession } from './browser/session.js';
import { createMainWindowController } from './main-window.js';
Expand DownExpand Up@@ -211,16 +215,20 @@ import { registerNotificationsIpc } from './notifications-ipc-main.js';
// asar-packaged builds; MAKA_E2E_USER_DATA_DIR must also be set, so the fake
// backend can't write test sessions into a real profile if someone sets only
// MAKA_E2E.
const isE2e =
const hasIsolatedE2eProfile =
!app.isPackaged &&
process.env.MAKA_E2E === '1' &&
!!process.env.MAKA_E2E_USER_DATA_DIR;
const isE2e = hasIsolatedE2eProfile && process.env.MAKA_E2E === '1';
const isComputerUseRealModelE2e =
hasIsolatedE2eProfile &&
process.env.MAKA_CU_REAL_MODEL_E2E === '1';
const isIsolatedE2e = isE2e || isComputerUseRealModelE2e;

// E2E isolation: redirect userData BEFORE the single-instance lock so the
// lock judges the throwaway dir, not the real user data — otherwise a
// developer with Maka open makes the E2E process exit as a "second instance".
// Gated by isE2e (not just the dir env) so a packaged build ignores it.
if (isE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
if (isIsolatedE2e && process.env.MAKA_E2E_USER_DATA_DIR) {
app.setPath('userData', process.env.MAKA_E2E_USER_DATA_DIR);
}

Expand DownExpand Up@@ -479,7 +487,7 @@ const systemPromptService = createSystemPromptMainService({
// BeginFrames on Linux, which stalls content-visibility inflation and any
// frame-paced E2E protocol (measured in the scroll-geometry climb: 38 frames
// over 31s). The E2E harness sets it, not the workflow — see fixtures.ts.
const startHidden = (Boolean(visualSmokeFixture) || isE2e)
const startHidden = (Boolean(visualSmokeFixture) || isIsolatedE2e)
&& process.env.MAKA_E2E_SHOW_WINDOW !== '1';
let onMainWindowClose = (): void => {};
const mainWindowController = createMainWindowController({
Expand DownExpand Up@@ -568,10 +576,31 @@ const computerUseHost = createComputerUseHost({
}
},
physicalInputRecentlyActive: () => powerMonitor.getSystemIdleTime() < 1,
...(isComputerUseRealModelE2e
? {
onTrace: (event) => {
const tracePath = process.env.MAKA_CU_REAL_MODEL_TRACE;
if (!tracePath) return;
void import('node:fs/promises').then(({ appendFile }) =>
appendFile(tracePath, `${JSON.stringify(event)}\n`, {
encoding: 'utf8',
mode: 0o600,
}),
).catch(() => {});
},
}
: {}),
overlay: createComputerUseOverlayHook(computerUseOverlay),
});
const computerUse = computerUseHost.selected;
const computerUseTools = computerUse.tools;
const computerUseTools = applyComputerUseRealModelPolicy(
computerUse.tools,
isComputerUseRealModelE2e
? parseComputerUseRealModelPolicy(
process.env.MAKA_CU_REAL_MODEL_POLICY,
)
: undefined,
);
const agentTools: MakaTool[] = [buildSubagentSpawnTool(), ...buildSubagentProjectionTools()];
const deferredTools: MakaTool[] = [
...riveTools,
Expand DownExpand Up@@ -795,6 +824,12 @@ backends.register('ai-sdk', async (ctx) => {
const modelFetch = buildSubscriptionModelFetch(connection, ctx.sessionId, model);
const memoryPromptSnapshot = await systemPromptService.buildLocalMemoryPromptFragment();
const supportsVision = modelSupportsVision(connection, model);
const backendTools = isComputerUseRealModelE2e
? computerUseTools
: [...(ctx.tools ?? builtinTools)];
const backendToolAvailability = isComputerUseRealModelE2e
? { economy: false, groups: [] }
: toolAvailability;

return new AiSdkBackend({
sessionId: ctx.sessionId,
Expand All@@ -805,8 +840,8 @@ backends.register('ai-sdk', async (ctx) => {
modelId: model,
permissionEngine,
modelFactory: (input) => getAIModel({ ...input, fetch: modelFetch }),
tools: [...(ctx.tools ?? builtinTools)],
toolAvailability,
tools: backendTools,
toolAvailability: backendToolAvailability,
spawnChildAgent: (input) => runtime.spawnChildAgent(ctx.sessionId, input),
listChildAgents: () => runtime.listChildAgents(ctx.sessionId),
readChildAgentOutput: (input) => runtime.readChildAgentOutput(ctx.sessionId, input),
Expand DownExpand Up@@ -2080,7 +2115,7 @@ app.whenReady().then(async () => {
// builds get the icon via .app bundle Info.plist; this covers the
// dev path.
if (process.platform === 'darwin' && app.dock) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isE2e) {
if (process.env.MAKA_VISUAL_SMOKE_FIXTURE || isIsolatedE2e) {
// PR-VISUAL-SMOKE-HEADLESS: hide the dock icon so the spawned
// Electron runs as an accessory app — no dock bounce, and it
// never becomes frontmost / steals focus from the developer's
Expand Down
90 changes: 90 additions & 0 deletions docs/computer-use-provider-evidence.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
# Computer Use Provider Evidence

This layer defines the evidence contract for real-model Computer Use runs. It
does not claim that any provider has completed a real run.

## Scenario Contract

The scenario library defines:

- an owned Electron fixture;
- the exact user prompt and expected state;
- forbidden effects;
- allowed actions and per-action budgets;
- required execution capabilities;
- deterministic state evaluation.

The fixture helper imports Electron only. It does not import Maka Runtime,
provider transports, or execution backends.

## Report Contract

Reports separate three evidence classes:

- `real-runtime`: a live provider model used the production Maka runtime;
- `hermetic-protocol`: a fake transport proved protocol behavior;
- `static-contract`: source or schema checks only.

Only `real-runtime` can satisfy a provider matrix cell marked `real`.
Policy-bypassed runs remain visibly labeled and cannot become an unqualified
pass.

The sanitizer preserves action types, timing, result codes, aggregate state,
and allowlisted trace fields. It removes coordinates, typed text, raw UI
content, credentials, full URLs, and provider payloads.

## Next Layer

A provider launcher must:

1. pin a scenario from this library;
2. run against the owned fixture and production Computer Use backend;
3. enforce the scenario action budget before dispatch;
4. emit a sanitized `real-runtime` report;
5. let the provider matrix validate fixture state and forbidden effects.

## First Real Run

The first qualifying run completed with:

- provider: OpenAI;
- model: `gpt-5.4`;
- evidence class: `real-runtime`;
- tool exposure: direct E2E, with only the production `maka_computer` tool;
- action: one app-scoped `observe`;
- tool latency: 1117 ms;
- total run latency: 7502 ms;
- terminal status: `complete / end_turn`;
- fixture oracle: verification code matched and interaction count remained zero.

The direct E2E tool exposure is deliberate. The default deferred `load_tools`
path remains a separate product contract; the launcher narrows provider
variables while still exercising the production tool implementation, permission
engine, Runtime, Desktop host, and cua-driver backend.

During this run, OpenAI Responses tool continuation exposed a product bug:
server-side storage generated an `item_reference` in the second request without
a `previous_response_id`, so custom Responses endpoints rejected the tool
result. OpenAI provider options now use `store:false`, matching the existing
Codex subscription boundary and keeping function calls/results inline.

The next run should perform one AX semantic mutation after executor hardening is
merged.

That L1 run has now completed:

- scenario: `l1-single-click`;
- provider/model: OpenAI `gpt-5.4`;
- actions: two observations and one `click_element`;
- no coordinate or compatibility input action was allowed;
- semantic click latency: 1445 ms;
- total run latency: 26023 ms;
- fixture oracle: primary click count 1, danger click count 0, over-click count
0;
- terminal status: `complete / end_turn`;
- result: pass.

The run initially failed closed when the user's foreground ChatGPT window
occluded the synthetic target. The fixture host now settles and raises its
layer-0 window with `showInactive()` and `moveTop()` before declaring readiness,
without focusing it or using an always-on-top overlay layer.
Loading
Loading