@@ -24,6 +24,14 @@ import {
2424 resolveQaOwnerPluginIdsForProviderIds ,
2525 resolveQaRuntimeHostVersion ,
2626} from "./bundled-plugin-staging.js" ;
27+ import {
28+ appendQaChildOutput ,
29+ appendQaChildOutputTail ,
30+ createQaChildOutputCapture ,
31+ createQaChildOutputTail ,
32+ formatQaChildOutputTail ,
33+ readQaChildOutput ,
34+ } from "./child-output.js" ;
2735import { assertRepoBoundPath , ensureRepoBoundDirectory } from "./cli-paths.js" ;
2836import { QaSuiteInfraError , toQaErrorObject } from "./errors.js" ;
2937import { formatQaGatewayLogsForError , redactQaGatewayDebugText } from "./gateway-log-redaction.js" ;
@@ -86,6 +94,66 @@ export type QaGatewayChildListeningContext = {
8694 runtimeEnv : NodeJS . ProcessEnv ;
8795} ;
8896
97+ function createQaGatewayEmptyTransport ( ) {
98+ return {
99+ requiredPluginIds : [ ] as const ,
100+ createGatewayConfig : ( ) => ( { } ) ,
101+ } satisfies Pick < QaTransportAdapter , "requiredPluginIds" | "createGatewayConfig" > ;
102+ }
103+
104+ function resolveQaGatewayChildCommand ( repoRoot : string ) : QaGatewayChildCommand {
105+ for ( const relativePath of [ "dist/index.mjs" , "dist/index.js" ] ) {
106+ const entryPath = path . join ( repoRoot , relativePath ) ;
107+ if ( existsSync ( entryPath ) ) {
108+ return {
109+ executablePath : process . execPath ,
110+ argsPrefix : [ entryPath ] ,
111+ cwd : repoRoot ,
112+ usePackagedPlugins : true ,
113+ } ;
114+ }
115+ }
116+
117+ const sourceEntryPath = path . join ( repoRoot , "src/entry.ts" ) ;
118+ if ( existsSync ( sourceEntryPath ) ) {
119+ return {
120+ executablePath : process . execPath ,
121+ argsPrefix : [ "--import" , "tsx" , sourceEntryPath ] ,
122+ cwd : repoRoot ,
123+ } ;
124+ }
125+
126+ throw new Error ( "OpenClaw CLI entry not found: expected dist/index.(m)js or src/entry.ts" ) ;
127+ }
128+
129+ async function runQaGatewayCliCommand ( params : {
130+ executablePath : string ;
131+ argsPrefix : readonly string [ ] ;
132+ args : readonly string [ ] ;
133+ cwd : string ;
134+ env : NodeJS . ProcessEnv ;
135+ } ) : Promise < string > {
136+ const stdout = createQaChildOutputCapture ( ) ;
137+ const stderr = createQaChildOutputTail ( ) ;
138+ const child = spawn ( params . executablePath , [ ...params . argsPrefix , ...params . args ] , {
139+ cwd : params . cwd ,
140+ env : { ...params . env , OPENCLAW_CLI : "1" } ,
141+ stdio : [ "ignore" , "pipe" , "pipe" ] ,
142+ } ) ;
143+ child . stdout . on ( "data" , ( chunk ) => appendQaChildOutput ( stdout , chunk ) ) ;
144+ child . stderr . on ( "data" , ( chunk ) => appendQaChildOutputTail ( stderr , chunk ) ) ;
145+ const exitCode = await new Promise < number > ( ( resolve , reject ) => {
146+ child . once ( "error" , reject ) ;
147+ child . once ( "close" , ( code ) => resolve ( code ?? 1 ) ) ;
148+ } ) ;
149+ const stdoutText = readQaChildOutput ( stdout ) ;
150+ if ( exitCode !== 0 ) {
151+ const stderrText = formatQaChildOutputTail ( stderr , "stderr" ) ;
152+ throw new Error ( `OpenClaw CLI exited ${ exitCode } : ${ stderrText || stdoutText } ` ) ;
153+ }
154+ return stdoutText ;
155+ }
156+
89157async function getFreePort ( ) {
90158 return await new Promise < number > ( ( resolve , reject ) => {
91159 const server = net . createServer ( ) ;
@@ -378,6 +446,8 @@ export const testing = {
378446 redactQaGatewayDebugText,
379447 readQaLiveProviderConfigOverrides,
380448 resolveQaGatewayChildProviderMode,
449+ resolveQaGatewayChildCommand,
450+ createQaGatewayEmptyTransport,
381451 assertQaLiveCodexAuthAvailable,
382452 stageQaLiveApiKeyProfiles,
383453 stageQaLiveAnthropicSetupToken,
@@ -387,6 +457,7 @@ export const testing = {
387457 resolveQaOwnerPluginIdsForProviderIds,
388458 resolveQaBundledPluginSourceDir,
389459 resolveQaRuntimeHostVersion,
460+ runQaGatewayCliCommand,
390461 createQaGatewayChildLogCollector,
391462 createQaBundledPluginsDir,
392463 signalQaGatewayChildProcessTree,
@@ -671,8 +742,9 @@ export function resolveQaControlUiRoot(params: { repoRoot: string; controlUiEnab
671742export async function startQaGatewayChild ( params : {
672743 repoRoot : string ;
673744 command ?: QaGatewayChildCommand ;
745+ useRepoCli ?: boolean ;
674746 providerBaseUrl ?: string ;
675- transport : Pick < QaTransportAdapter , "requiredPluginIds" | "createGatewayConfig" > ;
747+ transport ? : Pick < QaTransportAdapter , "requiredPluginIds" | "createGatewayConfig" > ;
676748 transportBaseUrl : string ;
677749 controlUiAllowedOrigins ?: string [ ] ;
678750 providerMode ?: QaProviderMode ;
@@ -694,7 +766,9 @@ export async function startQaGatewayChild(params: {
694766 ) ;
695767 const runtimeCwd = tempRoot ;
696768 const distEntryPath = path . join ( params . repoRoot , "dist" , "index.js" ) ;
697- const gatewayCommand = params . command ;
769+ const gatewayCommand =
770+ params . command ??
771+ ( params . useRepoCli ? resolveQaGatewayChildCommand ( params . repoRoot ) : undefined ) ;
698772 const gatewayExecutablePath = gatewayCommand ?. executablePath ;
699773 const gatewayArgsPrefix = gatewayCommand ?. argsPrefix ?? [ ] ;
700774 const gatewayArgsSuffix = gatewayCommand ?. argsSuffix ?? [ ] ;
@@ -707,6 +781,7 @@ export async function startQaGatewayChild(params: {
707781 const xdgCacheHome = path . join ( tempRoot , "xdg-cache" ) ;
708782 const configPath = path . join ( tempRoot , "openclaw.json" ) ;
709783 const gatewayToken = `qa-suite-${ randomUUID ( ) } ` ;
784+ const transport = params . transport ?? createQaGatewayEmptyTransport ( ) ;
710785 await seedQaAgentWorkspace ( {
711786 workspaceDir,
712787 repoRoot : params . repoRoot ,
@@ -757,8 +832,8 @@ export async function startQaGatewayChild(params: {
757832 primaryModel : params . primaryModel ,
758833 alternateModel : params . alternateModel ,
759834 enabledPluginIds,
760- transportPluginIds : params . transport . requiredPluginIds ,
761- transportConfig : params . transport . createGatewayConfig ( {
835+ transportPluginIds : transport . requiredPluginIds ,
836+ transportConfig : transport . createGatewayConfig ( {
762837 baseUrl : params . transportBaseUrl ,
763838 } ) ,
764839 liveProviderConfigs,
@@ -809,8 +884,11 @@ export async function startQaGatewayChild(params: {
809884
810885 try {
811886 const nodeExecPath = gatewayExecutablePath ?? ( await resolveQaNodeExecPath ( ) ) ;
887+ const cliArgsPrefix = gatewayExecutablePath
888+ ? gatewayArgsPrefix
889+ : [ distEntryPath , ...gatewayArgsPrefix ] ;
812890 const buildGatewayArgs = ( ) => [
813- ...( gatewayExecutablePath ? gatewayArgsPrefix : [ distEntryPath , ... gatewayArgsPrefix ] ) ,
891+ ...cliArgsPrefix ,
814892 "gateway" ,
815893 "run" ,
816894 "--port" ,
@@ -1104,6 +1182,15 @@ export async function startQaGatewayChild(params: {
11041182 configPath,
11051183 runtimeEnv : runningEnv ,
11061184 logs,
1185+ runCli ( args : readonly string [ ] ) {
1186+ return runQaGatewayCliCommand ( {
1187+ executablePath : nodeExecPath ,
1188+ argsPrefix : cliArgsPrefix ,
1189+ args,
1190+ cwd : gatewayCwd ,
1191+ env : runningEnv ,
1192+ } ) ;
1193+ } ,
11071194 signalProcess ( signal : NodeJS . Signals ) {
11081195 if ( ! activeChild . pid ) {
11091196 throw new Error ( "qa gateway child has no pid" ) ;
0 commit comments