diff --git a/packages/core/src/tools/computer-use/bootstrap.test.ts b/packages/core/src/tools/computer-use/bootstrap.test.ts index 375645255da..5adf7fb351c 100644 --- a/packages/core/src/tools/computer-use/bootstrap.test.ts +++ b/packages/core/src/tools/computer-use/bootstrap.test.ts @@ -34,10 +34,11 @@ describe('runBootstrap', () => { tmpHome = mkdtempSync(join(tmpdir(), 'qwen-cu-bs-')); deps = { homeDir: tmpHome, - packageSpec: 'open-computer-use@^0.3.0', + packageSpec: '@qwen-code/open-computer-use@^0.3.0', platform: 'darwin', promptInstallApproval: vi.fn(async () => true), probePermissions: vi.fn(async () => 'ok' as const), + probePermissionStatus: vi.fn(async () => 'ok' as const), }; }); @@ -48,7 +49,7 @@ describe('runBootstrap', () => { it('starts the client when binary is approved + permissions ok', async () => { const { saveInstallState } = await import('./install-state.js'); await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); @@ -99,20 +100,27 @@ describe('runBootstrap', () => { const { loadInstallState } = await import('./install-state.js'); const state = await loadInstallState(tmpHome); - expect(state?.approvedPackageSpec).toBe('open-computer-use@^0.3.0'); + expect(state?.approvedPackageSpec).toBe( + '@qwen-code/open-computer-use@^0.3.0', + ); }); - it('polls probePermissions when permissions are missing then granted', async () => { + it('shows the window once via doctor, then polls the window-free permission-status until granted', async () => { const { saveInstallState } = await import('./install-state.js'); await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); - let probeCount = 0; - deps.probePermissions = vi.fn(async () => { - probeCount++; - return probeCount < 3 ? 'accessibility' : 'ok'; + // Initial probe (doctor) reports missing → enters the poll loop and + // launches the onboarding window ONCE. + deps.probePermissions = vi.fn(async () => 'accessibility' as const); + // Poll probe (permission-status, window-free) reports missing twice + // then granted. + let pollCount = 0; + deps.probePermissionStatus = vi.fn(async () => { + pollCount++; + return pollCount < 2 ? 'accessibility' : 'ok'; }); deps.pollIntervalMs = 1; // speed up test deps.pollTimeoutMs = 1000; @@ -124,23 +132,26 @@ describe('runBootstrap', () => { deps, ); - // probe is called by bootstrap (each call is a doctor invocation in - // production, but here it's a mock). Doctor itself launches the - // onboarding window when needed — no separate spawnDoctor step. - expect(probeCount).toBeGreaterThanOrEqual(3); - expect(deps.probePermissions).toHaveBeenCalledWith( - 'open-computer-use@^0.3.0', + // doctor (window) called exactly once; the window-free status command + // is what gets polled — never doctor again (no window storm). + expect(deps.probePermissions).toHaveBeenCalledTimes(1); + expect(pollCount).toBeGreaterThanOrEqual(2); + expect(deps.probePermissionStatus).toHaveBeenCalledWith( + '@qwen-code/open-computer-use@^0.3.0', ); }); it('throws after pollTimeoutMs when permissions never grant', async () => { const { saveInstallState } = await import('./install-state.js'); await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); + // Initial doctor reports missing (enters loop); window-free poll never + // grants → must time out. deps.probePermissions = vi.fn(async () => 'accessibility' as const); + deps.probePermissionStatus = vi.fn(async () => 'accessibility' as const); deps.pollIntervalMs = 1; deps.pollTimeoutMs = 50; @@ -157,7 +168,7 @@ describe('runBootstrap', () => { it('skips permission flow on non-darwin platforms', async () => { const { saveInstallState } = await import('./install-state.js'); await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); deps.platform = 'linux'; @@ -170,21 +181,23 @@ describe('runBootstrap', () => { ); expect(deps.probePermissions).not.toHaveBeenCalled(); + expect(deps.probePermissionStatus).not.toHaveBeenCalled(); }); it('emits a fresh updateOutput message when permission kind changes mid-poll', async () => { const { saveInstallState } = await import('./install-state.js'); await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); - // Probe sequence: accessibility → screenRecording → ok - let probeCount = 0; - deps.probePermissions = vi.fn(async () => { - probeCount++; - if (probeCount === 1) return 'accessibility' as const; - if (probeCount === 2) return 'screenRecording' as const; + // Initial doctor reports accessibility missing (enters loop, window once). + deps.probePermissions = vi.fn(async () => 'accessibility' as const); + // Window-free poll sequence: accessibility → screenRecording → ok + let pollCount = 0; + deps.probePermissionStatus = vi.fn(async () => { + pollCount++; + if (pollCount === 1) return 'screenRecording' as const; return 'ok' as const; }); deps.pollIntervalMs = 1; @@ -202,8 +215,7 @@ describe('runBootstrap', () => { ); // The transition (accessibility → screenRecording) must emit a - // user-facing message naming the new permission kind. LaunchServices - // dedups doctor's window so we don't need a separate spawn step. + // user-facing message naming the new permission kind. expect(messages.some((m) => m.includes('screenRecording'))).toBe(true); expect(messages.some((m) => m.includes('accessibility'))).toBe(true); }); @@ -215,7 +227,7 @@ describe('runBootstrap', () => { // probe fire only on a fresh client start. const { saveInstallState } = await import('./install-state.js'); await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); @@ -235,6 +247,7 @@ describe('runBootstrap', () => { expect(startSpy).not.toHaveBeenCalled(); expect(deps.probePermissions).not.toHaveBeenCalled(); + expect(deps.probePermissionStatus).not.toHaveBeenCalled(); }); }); diff --git a/packages/core/src/tools/computer-use/bootstrap.ts b/packages/core/src/tools/computer-use/bootstrap.ts index 39eea208cfc..cbbf66b2134 100644 --- a/packages/core/src/tools/computer-use/bootstrap.ts +++ b/packages/core/src/tools/computer-use/bootstrap.ts @@ -10,20 +10,22 @@ * On first invocation of any computer_use__* tool: * 1. If not yet approved: prompt the user to install (one-time). * 2. Start the client (lazy npx spawn, may take ~60s first time). - * 3. On macOS only: probe permissions via the upstream `doctor` CLI - * (NOT via get_app_state, which has the side-effect of activating - * the target app — earlier rounds probed Finder this way and - * caused Finder to pop to the foreground at session start). The - * doctor command: - * - reads TCC + runtime preflight, prints - * "Permissions: accessibility=granted, screenRecording=missing" - * to stdout, then exits cleanly - * - launches the onboarding window via LaunchServices when any - * permission is missing — LaunchServices dedups so repeated - * invocations just bring the existing window to front - * We parse stdout for the probe result and rely on doctor's own - * window launching for the UX trigger — no separate spawnDoctor - * call needed. + * 3. On macOS only: probe permissions via the upstream CLI (NOT via + * get_app_state, which has the side-effect of activating the target + * app — earlier rounds probed Finder this way and caused Finder to + * pop to the foreground at session start). Two distinct commands: + * - `doctor` (initial probe, once): reads TCC + runtime preflight, + * prints "Permissions: accessibility=..., screenRecording=..." + * to stdout, AND launches the onboarding window when any + * permission is missing. This is what shows the window — once. + * - `permission-status` (poll probe): prints the same summary but + * NEVER launches a window. The poll loop uses this so it does + * not spawn a new onboarding window on every iteration. + * `doctor` does NOT dedup its window — each invocation launches a + * fresh one — so polling `doctor` flooded the screen with windows. + * Probing the window-free `permission-status` in the loop fixes that + * while keeping the "grant then auto-continue" UX. + * Requires @qwen-code/open-computer-use >= 0.2.2 for permission-status. */ import { execFile } from 'node:child_process'; @@ -60,11 +62,20 @@ export interface BootstrapDeps { */ promptInstallApproval: (packageSpec: string) => Promise; /** - * Probe permissions by running the upstream doctor CLI and parsing - * its stdout summary. The probe itself triggers the onboarding window - * when permissions are missing — no separate spawnDoctor needed. + * Initial probe: runs `doctor`, which both reports status AND launches + * the onboarding window when permissions are missing. Called exactly + * once on a fresh client start so the onboarding window appears one time. */ probePermissions: (packageSpec: string) => Promise; + /** + * Poll probe: runs `permission-status`, which reports status WITHOUT + * launching the onboarding window. Called on every poll iteration while + * waiting for the user to grant permissions — using the window-free + * command here is what prevents the onboarding-window storm. + */ + probePermissionStatus: ( + packageSpec: string, + ) => Promise; /** Poll interval for the permission watcher. Default 5000ms. */ pollIntervalMs?: number; /** Total poll timeout. Default 10 min. */ @@ -134,6 +145,41 @@ export async function probePermissionsViaDoctor( } } +/** + * Probe macOS permissions via the `permission-status` CLI command — + * the window-free counterpart to `doctor`. + * + * `permission-status` prints the SAME summary line as `doctor` but NEVER + * launches the onboarding window. This is the probe the polling loop uses + * while waiting for the user to grant permissions: `doctor` re-launches a + * fresh onboarding window on every invocation (it does not dedup), so + * polling it every few seconds floods the screen with windows. We launch + * the window exactly once (the initial `doctor` probe) and then poll this + * window-free command. + * + * Requires `@qwen-code/open-computer-use@>=0.2.2`. On older pinned packages + * the command is unknown → npx exits non-zero → we return 'other', which + * the poll loop treats as non-blocking (exits the wait; the real tool call + * then surfaces any permission error). No window storm either way. + */ +export async function probePermissionStatusViaCLI( + packageSpec: string, +): Promise { + try { + const { stdout } = await execFileAsync( + 'npx', + ['-y', packageSpec, 'permission-status'], + { + timeout: 30000, + env: process.env as NodeJS.ProcessEnv, + }, + ); + return parseDoctorStdout(stdout); + } catch { + return 'other'; + } +} + /** Production defaults — instantiated lazily so tests can override per call. */ function defaultDeps(): BootstrapDeps { const packageSpec = resolveComputerUsePackageSpec(); @@ -153,6 +199,7 @@ function defaultDeps(): BootstrapDeps { return process.env['QWEN_COMPUTER_USE_AUTO_APPROVE'] === '1'; }, probePermissions: probePermissionsViaDoctor, + probePermissionStatus: probePermissionStatusViaCLI, }; } @@ -224,9 +271,11 @@ export async function runBootstrap( ); // Track the last probe kind so we can emit a fresh message on - // transition (e.g. accessibility → screenRecording). LaunchServices - // dedup ensures each subsequent doctor poll re-focuses the existing - // window — no separate spawnDoctor call needed. + // transition (e.g. accessibility → screenRecording). The onboarding + // window was launched once by the initial `doctor` probe above; the + // poll loop below uses the window-free `permission-status` command so + // it never spawns additional windows while the single onboarding + // window stays open. let lastProbeKind: PermissionProbeResult = probe; const startedAt = Date.now(); @@ -240,7 +289,8 @@ export async function runBootstrap( ); } await new Promise((resolve) => setTimeout(resolve, pollIntervalMs)); - const next = await deps.probePermissions(deps.packageSpec); + // Window-free status check — see probePermissionStatusViaCLI. + const next = await deps.probePermissionStatus(deps.packageSpec); if (next === 'ok' || next === 'other') return; if (next !== lastProbeKind) { diff --git a/packages/core/src/tools/computer-use/client.test.ts b/packages/core/src/tools/computer-use/client.test.ts index e6dc2d81fbd..6cdaabe8bd5 100644 --- a/packages/core/src/tools/computer-use/client.test.ts +++ b/packages/core/src/tools/computer-use/client.test.ts @@ -5,7 +5,7 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; describe('ComputerUseClient', () => { it('is constructible', () => { const client = new ComputerUseClient({ - packageSpec: 'open-computer-use@latest', + packageSpec: '@qwen-code/open-computer-use@latest', onProgress: vi.fn(), }); expect(client).toBeDefined(); @@ -13,7 +13,7 @@ describe('ComputerUseClient', () => { it('reports not-started before start() is called', () => { const client = new ComputerUseClient({ - packageSpec: 'open-computer-use@latest', + packageSpec: '@qwen-code/open-computer-use@latest', onProgress: vi.fn(), }); expect(client.isStarted()).toBe(false); @@ -129,7 +129,7 @@ class ReconnectTestClient extends ComputerUseClient { function makeClient(): ReconnectTestClient { const c = new ReconnectTestClient({ - packageSpec: 'open-computer-use@latest', + packageSpec: '@qwen-code/open-computer-use@latest', }); // Pre-seed the started state so callTool guard passes. (c as unknown as { client: object }).client = { __fake: true }; diff --git a/packages/core/src/tools/computer-use/client.ts b/packages/core/src/tools/computer-use/client.ts index 85de4cbeec9..3cddc1d3b89 100644 --- a/packages/core/src/tools/computer-use/client.ts +++ b/packages/core/src/tools/computer-use/client.ts @@ -26,7 +26,7 @@ import { resolveComputerUsePackageSpec } from './constants.js'; * action. */ export interface ComputerUseClientOptions { - /** npm package spec to npx. Example: "open-computer-use@^0.3.0". */ + /** npm package spec to npx. Example: "@qwen-code/open-computer-use@0.2.3". */ packageSpec: string; /** Streaming hook for progress messages during slow operations. */ onProgress?: (message: string) => void; diff --git a/packages/core/src/tools/computer-use/constants.test.ts b/packages/core/src/tools/computer-use/constants.test.ts index ad4060bc45f..8fd08de66d6 100644 --- a/packages/core/src/tools/computer-use/constants.test.ts +++ b/packages/core/src/tools/computer-use/constants.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { + PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME, PINNED_OPEN_COMPUTER_USE_VERSION, resolveComputerUsePackageSpec, } from './constants.js'; @@ -38,16 +39,27 @@ describe('computer-use constants', () => { }); }); + describe('PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME', () => { + it('is the scoped QwenLM fork package', () => { + expect(PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME).toBe( + '@qwen-code/open-computer-use', + ); + }); + }); + describe('resolveComputerUsePackageSpec', () => { - it('defaults to open-computer-use@ when env var is unset', () => { + it('defaults to @ when env var is unset', () => { expect(resolveComputerUsePackageSpec()).toBe( - `open-computer-use@${PINNED_OPEN_COMPUTER_USE_VERSION}`, + `${PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME}@${PINNED_OPEN_COMPUTER_USE_VERSION}`, ); }); it('honors QWEN_COMPUTER_USE_PACKAGE override', () => { - process.env['QWEN_COMPUTER_USE_PACKAGE'] = 'open-computer-use@0.99.99'; - expect(resolveComputerUsePackageSpec()).toBe('open-computer-use@0.99.99'); + process.env['QWEN_COMPUTER_USE_PACKAGE'] = + '@qwen-code/open-computer-use@0.99.99'; + expect(resolveComputerUsePackageSpec()).toBe( + '@qwen-code/open-computer-use@0.99.99', + ); }); it('reads env var at call time (not at module load)', () => { diff --git a/packages/core/src/tools/computer-use/constants.ts b/packages/core/src/tools/computer-use/constants.ts index 62a506b1aaf..510c3536476 100644 --- a/packages/core/src/tools/computer-use/constants.ts +++ b/packages/core/src/tools/computer-use/constants.ts @@ -5,12 +5,30 @@ */ /** - * The exact upstream `open-computer-use` version this release of + * The npm package that provides the Computer Use MCP server. + * + * This is the QwenLM fork (`@qwen-code/open-computer-use`), not upstream + * `open-computer-use`. The fork rebrands the macOS bundle id, adds the + * `OPEN_COMPUTER_USE_IMAGE_*` screenshot env overrides, and strips + * Codex-specific install paths. Source: + * https://github.com/QwenLM/open-computer-use + * + * NOTE: only the npm *package* name is scoped. The CLI binary the + * package installs is still named `open-computer-use`, and the binary's + * own error strings (e.g. "Run `open-computer-use doctor`") use that + * binary name — `permission-detector.ts` matches against those and must + * NOT be re-scoped. + */ +export const PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME = + '@qwen-code/open-computer-use'; + +/** + * The exact `@qwen-code/open-computer-use` version this release of * qwen-code is pinned to. Hardcoded `schemas.ts` is generated against * this version; bumping it requires re-running the sync script. * * To bump: - * 1. Update this constant to the new version (e.g. '0.1.52'). + * 1. Update this constant to the new version (e.g. '0.2.1'). * 2. Run `npx tsx scripts/sync-computer-use-schemas.ts` from the * repo root — it reads this constant by default. * 3. Verify the regenerated `schemas.ts` diff is what you expect @@ -18,21 +36,21 @@ * 4. Manually smoke-test the e2e flow on macOS. * * Using an exact pin (NOT `^x.y.z` or `@latest`) is deliberate: - * upstream is 0.x and may ship schema-affecting changes in a patch + * the fork is 0.x and may ship schema-affecting changes in a patch * release. Locking the version means users get the exact schema - * surface we tested against; a new upstream release can't silently - * drift our hardcoded schemas out of sync. + * surface we tested against; a new release can't silently drift our + * hardcoded schemas out of sync. */ -export const PINNED_OPEN_COMPUTER_USE_VERSION = '0.1.51'; +export const PINNED_OPEN_COMPUTER_USE_VERSION = '0.2.3'; /** - * Resolve the upstream open-computer-use package spec to use for - * spawning the MCP server. Reads `QWEN_COMPUTER_USE_PACKAGE` env var - * at call time so tests / power users can override the pinned version. + * Resolve the package spec to `npx` for spawning the MCP server. Reads + * `QWEN_COMPUTER_USE_PACKAGE` env var at call time so tests / power + * users can override the pinned package or version. */ export function resolveComputerUsePackageSpec(): string { return ( process.env['QWEN_COMPUTER_USE_PACKAGE'] ?? - `open-computer-use@${PINNED_OPEN_COMPUTER_USE_VERSION}` + `${PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME}@${PINNED_OPEN_COMPUTER_USE_VERSION}` ); } diff --git a/packages/core/src/tools/computer-use/install-state.test.ts b/packages/core/src/tools/computer-use/install-state.test.ts index fb9a16900eb..8b3a50e93fc 100644 --- a/packages/core/src/tools/computer-use/install-state.test.ts +++ b/packages/core/src/tools/computer-use/install-state.test.ts @@ -26,39 +26,48 @@ describe('install-state', () => { it('round-trips state', async () => { await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); const loaded = await loadInstallState(tmpHome); expect(loaded).toEqual({ - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); }); it('isPackageSpecApproved returns false when no state', async () => { expect( - await isPackageSpecApproved(tmpHome, 'open-computer-use@^0.3.0'), + await isPackageSpecApproved( + tmpHome, + '@qwen-code/open-computer-use@^0.3.0', + ), ).toBe(false); }); it('isPackageSpecApproved returns true on exact match', async () => { await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); expect( - await isPackageSpecApproved(tmpHome, 'open-computer-use@^0.3.0'), + await isPackageSpecApproved( + tmpHome, + '@qwen-code/open-computer-use@^0.3.0', + ), ).toBe(true); }); it('isPackageSpecApproved returns false when version differs', async () => { await saveInstallState(tmpHome, { - approvedPackageSpec: 'open-computer-use@^0.3.0', + approvedPackageSpec: '@qwen-code/open-computer-use@^0.3.0', approvedAtIso: '2026-05-28T10:00:00Z', }); expect( - await isPackageSpecApproved(tmpHome, 'open-computer-use@^0.4.0'), + await isPackageSpecApproved( + tmpHome, + '@qwen-code/open-computer-use@^0.4.0', + ), ).toBe(false); }); diff --git a/packages/core/src/tools/computer-use/install-state.ts b/packages/core/src/tools/computer-use/install-state.ts index a3cf552733b..c13bc75b31f 100644 --- a/packages/core/src/tools/computer-use/install-state.ts +++ b/packages/core/src/tools/computer-use/install-state.ts @@ -9,7 +9,7 @@ import { homedir } from 'node:os'; import { join, dirname } from 'node:path'; export interface InstallState { - /** The package spec the user approved (e.g. "open-computer-use@^0.3.0"). */ + /** The package spec the user approved (e.g. "@qwen-code/open-computer-use@0.2.3"). */ approvedPackageSpec: string; /** ISO 8601 UTC timestamp of approval. */ approvedAtIso: string; diff --git a/packages/core/src/tools/computer-use/schemas.ts b/packages/core/src/tools/computer-use/schemas.ts index f702390c1be..d07ab9fd840 100644 --- a/packages/core/src/tools/computer-use/schemas.ts +++ b/packages/core/src/tools/computer-use/schemas.ts @@ -5,11 +5,20 @@ */ /** - * Hardcoded schemas for the upstream open-computer-use tools. + * Hardcoded schemas for the @qwen-code/open-computer-use tools. * - * Pinned to upstream: open-computer-use@0.1.51 - * (Exact pin — see PINNED_OPEN_COMPUTER_USE_VERSION in constants.ts - * for the canonical version and bump procedure.) + * Pinned to: @qwen-code/open-computer-use@0.2.3 + * (Exact pin — see PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME / + * PINNED_OPEN_COMPUTER_USE_VERSION in constants.ts for the canonical + * package + version and bump procedure.) + * + * Verified against the published package: `tools/list` returns the + * same 9 tools (click, drag, get_app_state, list_apps, + * perform_secondary_action, press_key, scroll, set_value, type_text). + * The 0.2.0 → 0.2.3 changes (notarization, the window-free + * `permission-status` CLI command, the screenshot minScale-clamp fix) do + * not alter the MCP tool surface, so these schemas are unchanged across + * all of them. * * Regenerated by scripts/sync-computer-use-schemas.ts — do not hand-edit. */ diff --git a/scripts/sync-computer-use-schemas.ts b/scripts/sync-computer-use-schemas.ts index a9891c29006..e8ce09218cf 100755 --- a/scripts/sync-computer-use-schemas.ts +++ b/scripts/sync-computer-use-schemas.ts @@ -1,19 +1,19 @@ #!/usr/bin/env tsx /** * Regenerate packages/core/src/tools/computer-use/schemas.ts from a - * live upstream open-computer-use MCP server. + * live @qwen-code/open-computer-use MCP server. * * Usage: * npx tsx scripts/sync-computer-use-schemas.ts [packageSpec] * - * The default is the currently-pinned version from + * The default is the currently-pinned package + version from * `packages/core/src/tools/computer-use/constants.ts` - * (PINNED_OPEN_COMPUTER_USE_VERSION). Running with no args verifies - * the current pin is still in sync; pass an explicit version - * (e.g. `open-computer-use@0.1.52`) to upgrade. + * (PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME / _VERSION). Running with no + * args verifies the current pin is still in sync; pass an explicit spec + * (e.g. `@qwen-code/open-computer-use@0.2.1`) to upgrade. * - * Bumping the upstream pin is a 4-step procedure documented in - * constants.ts — read that JSDoc first. + * Bumping the pin is a 4-step procedure documented in constants.ts — + * read that JSDoc first. */ import { Client } from '@modelcontextprotocol/sdk/client/index.js'; @@ -21,15 +21,17 @@ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' import { writeFile } from 'node:fs/promises'; import { resolve } from 'node:path'; -// Keep in sync with PINNED_OPEN_COMPUTER_USE_VERSION in -// packages/core/src/tools/computer-use/constants.ts. Duplicated as a -// literal here because importing TypeScript from `scripts/` into the -// package tree adds tooling complexity for a single-string lookup. -const DEFAULT_PINNED_VERSION = '0.1.51'; +// Keep in sync with PINNED_OPEN_COMPUTER_USE_PACKAGE_NAME / +// PINNED_OPEN_COMPUTER_USE_VERSION in +// packages/core/src/tools/computer-use/constants.ts. Duplicated as +// literals here because importing TypeScript from `scripts/` into the +// package tree adds tooling complexity for a two-string lookup. +const DEFAULT_PACKAGE_NAME = '@qwen-code/open-computer-use'; +const DEFAULT_PINNED_VERSION = '0.2.3'; async function main(): Promise { const packageSpec = - process.argv[2] ?? `open-computer-use@${DEFAULT_PINNED_VERSION}`; + process.argv[2] ?? `${DEFAULT_PACKAGE_NAME}@${DEFAULT_PINNED_VERSION}`; const transport = new StdioClientTransport({ command: 'npx',