From a59bb62e3d79f3596e5cffeac8ac654f4758f196 Mon Sep 17 00:00:00 2001 From: jiachengzhen Date: Sun, 20 Sep 2026 15:50:50 +0800 Subject: [PATCH] fix: cover PR494 computer use contracts and tests --- .../src/computer-use-contracts.test.ts | 87 +++++++++++++++++++ .../src/core/agent-runtime/tools/base.ts | 2 + .../computer-use/desktop-onboarding-client.ts | 2 +- .../runner-tool-execution.test.ts | 1 + .../tests/renderer-context-menu.test.ts | 6 +- .../desktop/tests/startup-lifecycle.test.ts | 1 + 6 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 App/backend/local-api-contracts/src/computer-use-contracts.test.ts diff --git a/App/backend/local-api-contracts/src/computer-use-contracts.test.ts b/App/backend/local-api-contracts/src/computer-use-contracts.test.ts new file mode 100644 index 000000000..cbce01820 --- /dev/null +++ b/App/backend/local-api-contracts/src/computer-use-contracts.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, it } from "vitest"; +import { + COMPUTER_USE_ONBOARDING_PREFIX, + SCREEN_CAPTURE_PREFIX, + isComputerUseOnboardingRequest, + isComputerUsePermissions, + isComputerUseProbeTarget, + isScreenCaptureMessage, + isScreenCaptureRequest, + isScreenCaptureResult, +} from "./index.js"; + +describe("computer-use local API exports", () => { + it("exports and validates the permission onboarding messages", () => { + expect(COMPUTER_USE_ONBOARDING_PREFIX).toBe("memmy:computer-use-onboarding:"); + expect(isComputerUseOnboardingRequest({ + type: `${COMPUTER_USE_ONBOARDING_PREFIX}prepare`, + requestId: "prepare-1", + })).toBe(true); + expect(isComputerUseOnboardingRequest({ + type: `${COMPUTER_USE_ONBOARDING_PREFIX}guide`, + requestId: "guide-1", + reason: "accessibility", + helperApp: "/Applications/Open Computer Use.app", + canContinue: false, + })).toBe(true); + expect(isComputerUseOnboardingRequest({ + type: `${COMPUTER_USE_ONBOARDING_PREFIX}guide`, + requestId: "guide-1", + reason: "accessibility", + helperApp: "/tmp/helper.app", + })).toBe(false); + expect(isComputerUseOnboardingRequest({ + type: `${COMPUTER_USE_ONBOARDING_PREFIX}prepare`, + requestId: "prepare-1", + extra: true, + })).toBe(false); + }); + + it("bounds permission probe payloads and states", () => { + expect(isComputerUsePermissions({ + accessibility: "granted", + screenRecording: "required", + failure: "helperPauseFailed", + })).toBe(true); + expect(isComputerUsePermissions({ + accessibility: "granted", + screenRecording: "required", + unexpected: true, + })).toBe(false); + expect(isComputerUseProbeTarget({ app: "com.example.Helper", pid: 42 })).toBe(true); + expect(isComputerUseProbeTarget({ app: "/Applications/Helper.app", pid: 42 })).toBe(false); + expect(isComputerUseProbeTarget({ app: "com.example.Helper", pid: 0 })).toBe(false); + }); + + it("exports and validates screen capture requests and results", () => { + expect(SCREEN_CAPTURE_PREFIX).toBe("memmy:screen-capture:"); + const request = { + type: `${SCREEN_CAPTURE_PREFIX}request`, + requestId: "capture-1", + displayId: "7", + }; + expect(isScreenCaptureMessage(request)).toBe(true); + expect(isScreenCaptureRequest(request)).toBe(true); + expect(isScreenCaptureRequest({ ...request, displayId: "../../secret" })).toBe(false); + expect(isScreenCaptureRequest({ ...request, command: "open" })).toBe(false); + + expect(isScreenCaptureResult({ + ok: true, + pngBase64: "png", + displayId: "7", + bounds: { x: 0, y: 0, width: 1920, height: 1080 }, + width: 1280, + height: 720, + })).toBe(true); + expect(isScreenCaptureResult({ + ok: false, + code: "permission_required", + message: "Screen recording permission is required", + })).toBe(true); + expect(isScreenCaptureResult({ + ok: false, + code: "permission_required", + message: "x".repeat(2001), + })).toBe(false); + }); +}); diff --git a/App/memmy-agent/src/core/agent-runtime/tools/base.ts b/App/memmy-agent/src/core/agent-runtime/tools/base.ts index f039c6b0b..684eba28e 100644 --- a/App/memmy-agent/src/core/agent-runtime/tools/base.ts +++ b/App/memmy-agent/src/core/agent-runtime/tools/base.ts @@ -128,10 +128,12 @@ export abstract class Tool { } static enabled(ctx: any): boolean { + void ctx; return true; } static create(ctx?: any): Tool { + void ctx; return new (this as any)() as Tool; } diff --git a/App/memmy-agent/src/tools/computer-use/desktop-onboarding-client.ts b/App/memmy-agent/src/tools/computer-use/desktop-onboarding-client.ts index 65d54b970..bd204f65b 100644 --- a/App/memmy-agent/src/tools/computer-use/desktop-onboarding-client.ts +++ b/App/memmy-agent/src/tools/computer-use/desktop-onboarding-client.ts @@ -52,7 +52,7 @@ export class DesktopOnboardingClient { }; const cancel = () => { finish(); - if (this.ipc.connected) { try { this.ipc.send!({ type: `${PREFIX}cancel`, requestId }, () => undefined); } catch {} } + if (this.ipc.connected) { try { this.ipc.send!({ type: `${PREFIX}cancel`, requestId }, () => undefined); } catch { /* Ignore cleanup send failures. */ } } }; const receive = (reply: any) => { if (reply?.type === `${PREFIX}guide:result` && reply.requestId === requestId) finish(reply.approved === true); diff --git a/App/memmy-agent/tests/core/agent-runtime/runner-tool-execution.test.ts b/App/memmy-agent/tests/core/agent-runtime/runner-tool-execution.test.ts index 5c55be802..61c3ecc34 100644 --- a/App/memmy-agent/tests/core/agent-runtime/runner-tool-execution.test.ts +++ b/App/memmy-agent/tests/core/agent-runtime/runner-tool-execution.test.ts @@ -211,6 +211,7 @@ describe("AgentRunner tool execution", () => { } async execute(_params: Record = {}, context?: ToolExecutionContext): Promise { + void _params; if (context) receivedContexts.push(context); entered(); return await new Promise((resolve, reject) => { diff --git a/App/shell/desktop/tests/renderer-context-menu.test.ts b/App/shell/desktop/tests/renderer-context-menu.test.ts index e648cdf13..c2d433a79 100644 --- a/App/shell/desktop/tests/renderer-context-menu.test.ts +++ b/App/shell/desktop/tests/renderer-context-menu.test.ts @@ -85,7 +85,11 @@ describe("desktop renderer context menu", () => { it("wires the native renderer context menu into both desktop windows", () => { const source = readFileSync(mainSourcePath, "utf8"); - expect(source).toContain("clipboard, dialog, ipcMain, Menu"); + const electronImport = source.match(/import \{[^}]+\} from "electron";/su)?.[0] ?? ""; + expect(electronImport).toContain("clipboard"); + expect(electronImport).toContain("dialog"); + expect(electronImport).toContain("ipcMain"); + expect(electronImport).toContain("Menu"); expect(source).toContain('import { resolveRendererContextMenuCommands, resolveRendererContextMenuMaxLabelWidth, type RendererContextMenuCommand } from "./renderer-context-menu.js";'); expect(source).toContain("attachRendererContextMenu(targetMainWindow);"); expect(source).toContain("attachRendererContextMenu(petWindow);"); diff --git a/App/shell/desktop/tests/startup-lifecycle.test.ts b/App/shell/desktop/tests/startup-lifecycle.test.ts index eb1efc88b..e65877fcd 100644 --- a/App/shell/desktop/tests/startup-lifecycle.test.ts +++ b/App/shell/desktop/tests/startup-lifecycle.test.ts @@ -93,6 +93,7 @@ function setup(options: { delay?: number; error?: Error; apiError?: Error; clean resolveCurrentDesktopEdition: () => "cn", initLogger: noop, forceLightWindowChrome: noop, installPreparedRequiredUpdateBeforeBoot: async () => false, registerIpcHandlers: noop, installBundledCliIfNeeded: async () => {}, startPackagedRendererServerIfNeeded: async () => {}, + createDesktopScreenCapture: noop, createComputerUseOnboarding: noop, windowsDataLayout: null, startManagedRuntimeServices: async () => { if (options.delay) await new Promise(resolve => setTimeout(resolve, options.delay));