From 99dbf56af196b37cdfb2f11f90cf1edbccbb9dea Mon Sep 17 00:00:00 2001 From: Dan Martin <93075337+Mingqwqqaq@users.noreply.github.com> Date: Thu, 15 Oct 2026 20:39:55 +0800 Subject: [PATCH 1/3] fix(desktop): preserve Windows opencli launcher paths Generated-by: Codex --- .../src/main/__tests__/opencli-chrome.test.ts | 32 +++++++++++++++++-- apps/desktop/src/main/opencli-chrome.ts | 5 ++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/main/__tests__/opencli-chrome.test.ts b/apps/desktop/src/main/__tests__/opencli-chrome.test.ts index e221765b292..8bd38a00513 100644 --- a/apps/desktop/src/main/__tests__/opencli-chrome.test.ts +++ b/apps/desktop/src/main/__tests__/opencli-chrome.test.ts @@ -18,8 +18,8 @@ */ import assert from 'node:assert/strict'; -import { execFileSync } from 'node:child_process'; -import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; +import { execFileSync, spawnSync } from 'node:child_process'; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { test } from 'node:test'; @@ -45,10 +45,36 @@ test('Windows launchers set Node mode before starting the entry', () => { assert.equal(launchers.command, join(dir, 'opencli-mcp.cmd')); assert.equal( readFileSync(launchers.host, 'utf8'), - '@echo off\r\nset ELECTRON_RUN_AS_NODE=1\r\n"C:\\Maka\\Maka.exe" "C:\\Maka\\main.js" host\r\n', + '@echo off\r\nsetlocal DisableDelayedExpansion\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n"C:\\Maka\\Maka.exe" "C:\\Maka\\main.js" host\r\n', ); }); +test('Windows launchers preserve Unicode and batch metacharacters without adding protocol output', { skip: process.platform !== 'win32' }, (t) => { + const root = mkdtempSync(join(tmpdir(), 'maka-opencli-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + const directory = join(root, '中文 %MAKA_LAUNCHER_TEST% !MAKA_LAUNCHER_TEST! & files'); + mkdirSync(directory); + const entry = join(directory, 'entry.mjs'); + writeFileSync(entry, 'console.log(JSON.stringify({ args: process.argv.slice(2), node: process.env.ELECTRON_RUN_AS_NODE })); process.exitCode = 23;'); + writeOpencliLaunchers(directory, 'win32', process.execPath, entry); + for (const page of [936, 65001]) { + for (const [file, mode] of [['opencli-mcp.cmd', 'stdio'], ['opencli-mcp-host.cmd', 'host']]) { + const result = spawnSync(process.env.ComSpec ?? 'cmd.exe', ['/d', '/v:on', '/s', '/c', `"chcp ${page} >nul & ${file}"`], { + cwd: directory, + env: { ...process.env, MAKA_LAUNCHER_TEST: 'expanded' }, + encoding: 'utf8', + windowsHide: true, + windowsVerbatimArguments: true, + timeout: 10_000, + }); + assert.ifError(result.error); + assert.equal(result.status, 23, result.stderr); + assert.equal(result.stderr, ''); + assert.deepEqual(JSON.parse(result.stdout), { args: [mode], node: '1' }); + } + } +}); + test('the host manifest goes to Chrome and existing browsers, never over another live install', () => { const root = mkdtempSync(join(tmpdir(), 'maka-opencli-')); const host = join(root, 'opencli-mcp-host'); diff --git a/apps/desktop/src/main/opencli-chrome.ts b/apps/desktop/src/main/opencli-chrome.ts index bc4fba2d0d7..496cf1c04a1 100644 --- a/apps/desktop/src/main/opencli-chrome.ts +++ b/apps/desktop/src/main/opencli-chrome.ts @@ -64,7 +64,10 @@ export function writeOpencliLaunchers( const write = (name: string, args: string): string => { if (platform === 'win32') { const file = join(dir, `${name}.cmd`); - writeFileSync(file, `@echo off\r\nset ELECTRON_RUN_AS_NODE=1\r\n"${executable}" "${entry}" ${args}\r\n`); + // cmd reads batch files using its console code page, even when paths are + // quoted. Keep UTF-8 paths literal through both batch expansion phases. + const quotePath = (path: string) => `"${path.replaceAll('%', '%%')}"`; + writeFileSync(file, `@echo off\r\nsetlocal DisableDelayedExpansion\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n${quotePath(executable)} ${quotePath(entry)} ${args}\r\n`); return file; } const file = join(dir, name); From dd42e95ca20324378f6d4a1809715acdc6c09938 Mon Sep 17 00:00:00 2001 From: Dan Martin <93075337+Mingqwqqaq@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:13:39 +0800 Subject: [PATCH 2/3] fix(desktop): restore launcher console code page Restore the caller's console code page without changing child exit status or protocol output. Run the native launcher regression in Windows recovery CI. Generated-by: Codex --- .github/workflows/windows-recovery.yml | 7 +++++++ apps/desktop/src/main/__tests__/opencli-chrome.test.ts | 8 ++++++-- apps/desktop/src/main/opencli-chrome.ts | 4 +++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows-recovery.yml b/.github/workflows/windows-recovery.yml index fcdd28135e7..7ed3489acfa 100644 --- a/.github/workflows/windows-recovery.yml +++ b/.github/workflows/windows-recovery.yml @@ -61,6 +61,9 @@ on: - 'scripts/clean-build.mjs' - 'scripts/clean-paths.mjs' - 'scripts/windows-runtime-host-local-ipc-trust.ps1' + # The desktop launcher executes cmd.exe with native console code pages. + - 'apps/desktop/src/main/opencli-chrome.ts' + - 'apps/desktop/src/main/__tests__/opencli-chrome.test.ts' # Every file in the import closure of the suites below that branches on # `process.platform === 'win32'`. Generated, not curated: # `scripts/windows-package-source-closure.test.mjs` recomputes this set @@ -184,6 +187,10 @@ jobs: - name: Build test artifacts run: npm.cmd run build:test + - name: Verify Windows opencli launchers + shell: pwsh + run: node.exe --test apps/desktop/dist/main/__tests__/opencli-chrome.test.js + - name: Verify managed dependency alternate streams shell: pwsh run: | diff --git a/apps/desktop/src/main/__tests__/opencli-chrome.test.ts b/apps/desktop/src/main/__tests__/opencli-chrome.test.ts index 8bd38a00513..e612943986e 100644 --- a/apps/desktop/src/main/__tests__/opencli-chrome.test.ts +++ b/apps/desktop/src/main/__tests__/opencli-chrome.test.ts @@ -45,7 +45,7 @@ test('Windows launchers set Node mode before starting the entry', () => { assert.equal(launchers.command, join(dir, 'opencli-mcp.cmd')); assert.equal( readFileSync(launchers.host, 'utf8'), - '@echo off\r\nsetlocal DisableDelayedExpansion\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n"C:\\Maka\\Maka.exe" "C:\\Maka\\main.js" host\r\n', + '@echo off\r\nsetlocal DisableDelayedExpansion\r\nfor /f "tokens=2 delims=:" %%c in (\'chcp\') do set "MAKA_OPENCLI_CODE_PAGE=%%c"\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n"C:\\Maka\\Maka.exe" "C:\\Maka\\main.js" host\r\nset "MAKA_OPENCLI_EXIT_CODE=%ERRORLEVEL%"\r\nchcp %MAKA_OPENCLI_CODE_PAGE% >nul\r\nexit /b %MAKA_OPENCLI_EXIT_CODE%\r\n', ); }); @@ -59,7 +59,10 @@ test('Windows launchers preserve Unicode and batch metacharacters without adding writeOpencliLaunchers(directory, 'win32', process.execPath, entry); for (const page of [936, 65001]) { for (const [file, mode] of [['opencli-mcp.cmd', 'stdio'], ['opencli-mcp-host.cmd', 'host']]) { - const result = spawnSync(process.env.ComSpec ?? 'cmd.exe', ['/d', '/v:on', '/s', '/c', `"chcp ${page} >nul & ${file}"`], { + // CALL returns to the same console, where the caller must retain its code + // page as well as the child's exit status. Keep diagnostics off stdout. + writeFileSync(join(directory, 'caller.cmd'), `@echo off\r\nchcp ${page} >nul\r\ncall ${file}\r\nset "childExit=%errorlevel%"\r\nchcp >code-page.txt\r\nexit /b %childExit%\r\n`); + const result = spawnSync(process.env.ComSpec ?? 'cmd.exe', ['/d', '/v:on', '/s', '/c', 'caller.cmd'], { cwd: directory, env: { ...process.env, MAKA_LAUNCHER_TEST: 'expanded' }, encoding: 'utf8', @@ -71,6 +74,7 @@ test('Windows launchers preserve Unicode and batch metacharacters without adding assert.equal(result.status, 23, result.stderr); assert.equal(result.stderr, ''); assert.deepEqual(JSON.parse(result.stdout), { args: [mode], node: '1' }); + assert.equal(Number(readFileSync(join(directory, 'code-page.txt'), 'utf8').match(/:\s*(\d+)/u)?.[1]), page); } } }); diff --git a/apps/desktop/src/main/opencli-chrome.ts b/apps/desktop/src/main/opencli-chrome.ts index 496cf1c04a1..a003ca3270b 100644 --- a/apps/desktop/src/main/opencli-chrome.ts +++ b/apps/desktop/src/main/opencli-chrome.ts @@ -66,8 +66,10 @@ export function writeOpencliLaunchers( const file = join(dir, `${name}.cmd`); // cmd reads batch files using its console code page, even when paths are // quoted. Keep UTF-8 paths literal through both batch expansion phases. + // Unlike environment variables, the console code page outlives SETLOCAL. + // Restore it before returning, without losing the child's exit status. const quotePath = (path: string) => `"${path.replaceAll('%', '%%')}"`; - writeFileSync(file, `@echo off\r\nsetlocal DisableDelayedExpansion\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n${quotePath(executable)} ${quotePath(entry)} ${args}\r\n`); + writeFileSync(file, `@echo off\r\nsetlocal DisableDelayedExpansion\r\nfor /f "tokens=2 delims=:" %%c in ('chcp') do set "MAKA_OPENCLI_CODE_PAGE=%%c"\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n${quotePath(executable)} ${quotePath(entry)} ${args}\r\nset "MAKA_OPENCLI_EXIT_CODE=%ERRORLEVEL%"\r\nchcp %MAKA_OPENCLI_CODE_PAGE% >nul\r\nexit /b %MAKA_OPENCLI_EXIT_CODE%\r\n`); return file; } const file = join(dir, name); From 61e53211f4505293ad6f2e589ccc0e6cb4637584 Mon Sep 17 00:00:00 2001 From: Dan Martin <93075337+Mingqwqqaq@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:37:12 +0800 Subject: [PATCH 3/3] fix(desktop): parse localized launcher code page output Treat the trailing period in localized chcp reports as a delimiter. Exercise the generated batch parser with German query output while checking real Windows code-page restoration, protocol output and child exit status. Generated-by: Codex --- .../src/main/__tests__/opencli-chrome.test.ts | 29 ++++++++++++++++++- apps/desktop/src/main/opencli-chrome.ts | 3 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/main/__tests__/opencli-chrome.test.ts b/apps/desktop/src/main/__tests__/opencli-chrome.test.ts index e612943986e..f81ca22a142 100644 --- a/apps/desktop/src/main/__tests__/opencli-chrome.test.ts +++ b/apps/desktop/src/main/__tests__/opencli-chrome.test.ts @@ -45,7 +45,7 @@ test('Windows launchers set Node mode before starting the entry', () => { assert.equal(launchers.command, join(dir, 'opencli-mcp.cmd')); assert.equal( readFileSync(launchers.host, 'utf8'), - '@echo off\r\nsetlocal DisableDelayedExpansion\r\nfor /f "tokens=2 delims=:" %%c in (\'chcp\') do set "MAKA_OPENCLI_CODE_PAGE=%%c"\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n"C:\\Maka\\Maka.exe" "C:\\Maka\\main.js" host\r\nset "MAKA_OPENCLI_EXIT_CODE=%ERRORLEVEL%"\r\nchcp %MAKA_OPENCLI_CODE_PAGE% >nul\r\nexit /b %MAKA_OPENCLI_EXIT_CODE%\r\n', + '@echo off\r\nsetlocal DisableDelayedExpansion\r\nfor /f "tokens=2 delims=:." %%c in (\'chcp\') do set "MAKA_OPENCLI_CODE_PAGE=%%c"\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n"C:\\Maka\\Maka.exe" "C:\\Maka\\main.js" host\r\nset "MAKA_OPENCLI_EXIT_CODE=%ERRORLEVEL%"\r\nchcp %MAKA_OPENCLI_CODE_PAGE% >nul\r\nexit /b %MAKA_OPENCLI_EXIT_CODE%\r\n', ); }); @@ -79,6 +79,33 @@ test('Windows launchers preserve Unicode and batch metacharacters without adding } }); +test('Windows launchers restore code pages reported with localized trailing punctuation', { skip: process.platform !== 'win32' }, (t) => { + const directory = mkdtempSync(join(tmpdir(), 'maka-opencli-locale-')); + t.after(() => rmSync(directory, { recursive: true, force: true })); + const entry = join(directory, 'entry.mjs'); + writeFileSync(entry, 'process.stdout.write("protocol"); process.exitCode = 23;'); + for (const page of [850, 936, 65001]) { + const launchers = writeOpencliLaunchers(directory, 'win32', process.execPath, entry); + // Simulate only the localized query response; cmd.exe still executes the + // generated parser, changes the real code page and restores it on return. + const script = readFileSync(launchers.command, 'utf8'); + assert.ok(script.includes("in ('chcp')")); + writeFileSync(launchers.command, script.replace("in ('chcp')", `in ('echo Aktive Codepage: ${page}.')`)); + writeFileSync(join(directory, 'caller.cmd'), `@echo off\r\nchcp ${page} >nul\r\ncall opencli-mcp.cmd\r\nset "childExit=%errorlevel%"\r\nchcp >code-page.txt\r\nexit /b %childExit%\r\n`); + const result = spawnSync(process.env.ComSpec ?? 'cmd.exe', ['/d', '/v:on', '/c', 'caller.cmd'], { + cwd: directory, + encoding: 'utf8', + windowsHide: true, + timeout: 10_000, + }); + assert.ifError(result.error); + assert.equal(result.status, 23, result.stderr); + assert.equal(result.stderr, ''); + assert.equal(result.stdout, 'protocol'); + assert.equal(Number(readFileSync(join(directory, 'code-page.txt'), 'utf8').match(/:\s*(\d+)/u)?.[1]), page); + } +}); + test('the host manifest goes to Chrome and existing browsers, never over another live install', () => { const root = mkdtempSync(join(tmpdir(), 'maka-opencli-')); const host = join(root, 'opencli-mcp-host'); diff --git a/apps/desktop/src/main/opencli-chrome.ts b/apps/desktop/src/main/opencli-chrome.ts index a003ca3270b..32bac933ccf 100644 --- a/apps/desktop/src/main/opencli-chrome.ts +++ b/apps/desktop/src/main/opencli-chrome.ts @@ -68,8 +68,9 @@ export function writeOpencliLaunchers( // quoted. Keep UTF-8 paths literal through both batch expansion phases. // Unlike environment variables, the console code page outlives SETLOCAL. // Restore it before returning, without losing the child's exit status. + // chcp's localized response can end the numeric page with a period. const quotePath = (path: string) => `"${path.replaceAll('%', '%%')}"`; - writeFileSync(file, `@echo off\r\nsetlocal DisableDelayedExpansion\r\nfor /f "tokens=2 delims=:" %%c in ('chcp') do set "MAKA_OPENCLI_CODE_PAGE=%%c"\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n${quotePath(executable)} ${quotePath(entry)} ${args}\r\nset "MAKA_OPENCLI_EXIT_CODE=%ERRORLEVEL%"\r\nchcp %MAKA_OPENCLI_CODE_PAGE% >nul\r\nexit /b %MAKA_OPENCLI_EXIT_CODE%\r\n`); + writeFileSync(file, `@echo off\r\nsetlocal DisableDelayedExpansion\r\nfor /f "tokens=2 delims=:." %%c in ('chcp') do set "MAKA_OPENCLI_CODE_PAGE=%%c"\r\nchcp 65001 >nul\r\nset ELECTRON_RUN_AS_NODE=1\r\n${quotePath(executable)} ${quotePath(entry)} ${args}\r\nset "MAKA_OPENCLI_EXIT_CODE=%ERRORLEVEL%"\r\nchcp %MAKA_OPENCLI_CODE_PAGE% >nul\r\nexit /b %MAKA_OPENCLI_EXIT_CODE%\r\n`); return file; } const file = join(dir, name);