From faa721fed8e9d0207e05d439a0f357c8832ded7d Mon Sep 17 00:00:00 2001 From: jesieleo <90036937+jesieleo@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:07:46 +0800 Subject: [PATCH] fix: preserve profile preparation before TTY dispatch --- packages/cli/src/cli.ts | 4 +++ packages/core/src/profiles/launch-service.ts | 24 ++++++++----- tests/main/windows-ccr-launcher.test.mjs | 37 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 tests/main/windows-ccr-launcher.test.mjs diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index fe66d047..cb1867c9 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -57,6 +57,7 @@ const serviceStopTimeoutMs = 10_000; const webAuthHeader = "x-ccr-web-auth"; const webAuthQueryParam = "ccr_web_token"; const defaultCliCommandName = "ccr"; +const prepareProfileOnlyEnv = "CCR_CLI_PREPARE_PROFILE_ONLY"; async function main(): Promise { const options = parseArgs(process.argv.slice(2)); @@ -123,6 +124,9 @@ async function main(): Promise { throw new Error(runtimeResult.message); } } + if (resolvedSurface === "cli" && process.env[prepareProfileOnlyEnv] === "1") { + return; + } if (profile.agent === "claude-code" && resolvedSurface === "app") { applyClaudeAppGatewayConfig(launchConfig); applyClaudeAppGatewayConfig(launchConfig, { diff --git a/packages/core/src/profiles/launch-service.ts b/packages/core/src/profiles/launch-service.ts index d84425f1..ff7b9a5a 100644 --- a/packages/core/src/profiles/launch-service.ts +++ b/packages/core/src/profiles/launch-service.ts @@ -1183,20 +1183,12 @@ function posixCcrLauncher(runtimeFile: string): string { ].join("\n") + "\n"; } -function windowsCcrLauncher(runtimeFile: string, config?: AppConfig): string { +export function windowsCcrLauncher(runtimeFile: string, config?: AppConfig): string { const nodePath = bundledNodePath(); const dispatches = config ? windowsProfileCliDispatches(config) : []; return [ "@echo off", "setlocal", - ...(dispatches.length > 0 - ? [ - "if /I \"%~2\"==\"app\" goto ccr_run_cli", - "if /I \"%~2\"==\"--app\" goto ccr_run_cli", - ...dispatches.map((dispatch, index) => `if /I \"%~1\"==\"${cmdValue(dispatch.profileRef)}\" goto ccr_profile_${index}`), - ":ccr_run_cli" - ] - : []), `set "${desktopCliCommandNameEnv}=${desktopCliCommandName}"`, `set "CCR_CLI_RUNTIME=${cmdEnvValue(runtimeFile)}"`, `set "CCR_CLI_NODE_PATH=${cmdEnvValue(nodePath)}"`, @@ -1205,6 +1197,14 @@ function windowsCcrLauncher(runtimeFile: string, config?: AppConfig): string { ") else (", " set \"NODE_PATH=%CCR_CLI_NODE_PATH%\"", ")", + ...(dispatches.length > 0 + ? [ + "if /I \"%~2\"==\"app\" goto ccr_run_cli", + "if /I \"%~2\"==\"--app\" goto ccr_run_cli", + ...dispatches.map((dispatch, index) => `if /I \"%~1\"==\"${cmdValue(dispatch.profileRef)}\" goto ccr_profile_${index}`), + ":ccr_run_cli" + ] + : []), "if defined CCR_NODE_BIN (", ' "%CCR_NODE_BIN%" "%CCR_CLI_RUNTIME%" %*', " exit /b %ERRORLEVEL%", @@ -1214,6 +1214,12 @@ function windowsCcrLauncher(runtimeFile: string, config?: AppConfig): string { "exit /b %ERRORLEVEL%", ...dispatches.flatMap((dispatch, index) => [ `:ccr_profile_${index}`, + "set \"CCR_CLI_PREPARE_PROFILE_ONLY=1\"", + "set \"ELECTRON_RUN_AS_NODE=1\"", + `${cmdQuote(process.execPath)} "%CCR_CLI_RUNTIME%" %*`, + "if errorlevel 1 exit /b %ERRORLEVEL%", + "set \"CCR_CLI_PREPARE_PROFILE_ONLY=\"", + "set \"ELECTRON_RUN_AS_NODE=\"", "set \"CCR_CLI_DIRECT_PROFILE_DISPATCH=1\"", `call ${cmdQuote(dispatch.launcher)} %*`, "exit /b %ERRORLEVEL%" diff --git a/tests/main/windows-ccr-launcher.test.mjs b/tests/main/windows-ccr-launcher.test.mjs new file mode 100644 index 00000000..a050bb43 --- /dev/null +++ b/tests/main/windows-ccr-launcher.test.mjs @@ -0,0 +1,37 @@ +import assert from "node:assert/strict"; +import path from "node:path"; +import test from "node:test"; +import { windowsCcrLauncher } from "../../packages/core/src/profiles/launch-service.ts"; + +test("Windows CCR launcher prepares CLI profiles before direct TTY dispatch", { skip: process.platform !== "win32" }, () => { + const config = { + profile: { + profiles: [ + { + agent: "claude-code", + enabled: true, + id: "claude-main", + model: "provider/model", + name: "Claude Main", + scope: "ccr", + surface: "cli" + } + ] + } + }; + const runtimeFile = path.join("C:\\CCR", "ccr-cli.js"); + const launcher = windowsCcrLauncher(runtimeFile, config); + + assert.match(launcher, /if \/I "%~1"=="Claude Main" goto ccr_profile_0/); + assert.match(launcher, /set "CCR_CLI_PREPARE_PROFILE_ONLY=1"/); + assert.match(launcher, /set "ELECTRON_RUN_AS_NODE=1"/); + assert.match(launcher, /set "CCR_CLI_DIRECT_PROFILE_DISPATCH=1"/); + assert.match(launcher, /call ".*ccr-claude-code-wrapper-claude-main\.cmd" %\*/); + + const prepareIndex = launcher.indexOf('set "CCR_CLI_PREPARE_PROFILE_ONLY=1"'); + const directDispatchIndex = launcher.indexOf('set "CCR_CLI_DIRECT_PROFILE_DISPATCH=1"'); + const wrapperIndex = launcher.indexOf("ccr-claude-code-wrapper-claude-main.cmd"); + assert.equal(prepareIndex >= 0, true); + assert.equal(directDispatchIndex > prepareIndex, true); + assert.equal(wrapperIndex > directDispatchIndex, true); +});