@@ -299,7 +299,7 @@ const quickLinksService = QuickLinksService.getInstance();
299299const recommendationsService = RecommendationsService . getInstance ( ) ;
300300const activityFeed = ActivityFeedService . getInstance ( ) ;
301301activityFeed . setIO ( io ) ;
302- activityFeed . track ( 'server.started' , { port : Number ( process . env . ORCHESTRATOR_PORT || 3000 ) } ) ;
302+ activityFeed . track ( 'server.started' , { port : Number ( process . env . ORCHESTRATOR_PORT || 9460 ) } ) ;
303303const productLauncherService = ProductLauncherService . getInstance ( ) ;
304304const conversationService = ConversationService . getInstance ( ) ;
305305const agentProviderService = AgentProviderService . getInstance ( { agentManager, logger } ) ;
@@ -7590,7 +7590,7 @@ app.post('/api/commander/execute-text', async (req, res) => {
75907590 return res . status ( 400 ) . json ( { ok : false , error : 'text too long' } ) ;
75917591 }
75927592
7593- // Keep the parser context in sync with Commander’ s current UI state.
7593+ // Keep the parser context in sync with Commander' s current UI state.
75947594 try {
75957595 const snapshot = commanderContextService . getSnapshot ( { workspaceManager, commanderService, commandRegistry } ) ;
75967596 voiceCommandService . setContext ( snapshot ?. context || { } ) ;
@@ -8107,7 +8107,8 @@ app.get('/replay-viewer/:worktreeId/*?', (req, res) => {
81078107} ) ;
81088108
81098109// Start server
8110- const PORT = Number ( process . env . ORCHESTRATOR_PORT || 3000 ) ;
8110+ const DESIRED_PORT = Number ( process . env . ORCHESTRATOR_PORT || 9460 ) ;
8111+ const MAX_PORT_ATTEMPTS = 10 ;
81118112const hostPolicy = evaluateBindSecurity ( {
81128113 host : process . env . ORCHESTRATOR_HOST || process . env . HOST ,
81138114 authToken : AUTH_TOKEN ,
@@ -8116,64 +8117,85 @@ const hostPolicy = evaluateBindSecurity({
81168117const HOST = hostPolicy . host ;
81178118
81188119if ( ! hostPolicy . allowStart ) {
8119- logger . error ( 'Refusing to bind to a non-loopback host without AUTH_TOKEN. Set AUTH_TOKEN or set ORCHESTRATOR_ALLOW_INSECURE_LAN_NO_AUTH=1 to override.' , { host : HOST , port : PORT } ) ;
8120+ logger . error ( 'Refusing to bind to a non-loopback host without AUTH_TOKEN. Set AUTH_TOKEN or set ORCHESTRATOR_ALLOW_INSECURE_LAN_NO_AUTH=1 to override.' , { host : HOST , port : DESIRED_PORT } ) ;
81208121 process . exit ( 1 ) ;
81218122}
81228123
8123- httpServer . listen ( PORT , HOST , ( ) => {
8124- logger . info ( `Server running on http://${ HOST } :${ PORT } ` ) ;
8125- if ( ! hostPolicy . isLoopback ) {
8126- const bindType = hostPolicy . isBindAll ? 'bind-all' : 'explicit-host' ;
8127- logger . info ( `LAN access enabled (${ bindType } ) on port ${ PORT } ` ) ;
8128- if ( ! hostPolicy . hasAuthToken ) {
8129- logger . warn ( 'LAN access is enabled without AUTH_TOKEN. This is insecure; anyone on the network can control this orchestrator.' , { host : HOST , port : PORT } ) ;
8124+ function tryListen ( port , attempt ) {
8125+ httpServer . listen ( port , HOST , ( ) => {
8126+ if ( port !== DESIRED_PORT ) {
8127+ logger . info ( `Port ${ DESIRED_PORT } in use, bound to port ${ port } instead` ) ;
81308128 }
8131- }
8132- if ( hostPolicy . hasAuthToken ) {
8133- logger . info ( 'Authentication enabled' ) ;
8134- }
8129+ logger . info ( `Server running on http://${ HOST } :${ port } ` ) ;
8130+ // Expose actual port for other services to discover
8131+ process . env . ORCHESTRATOR_PORT = String ( port ) ;
81358132
8136- // Start the Advanced Diff Viewer in the background.
8137- // Default: enabled, since users expect the 🔍 diff viewer to be ready without manual terminal steps.
8138- const autoStartRaw = String ( process . env . AUTO_START_DIFF_VIEWER ?? 'true' ) . toLowerCase ( ) ;
8139- const shouldAutoStartDiffViewer = ! [ '0' , 'false' , 'no' ] . includes ( autoStartRaw ) ;
8140- if ( shouldAutoStartDiffViewer ) {
8141- diffViewerService . ensureRunning ( ) . catch ( ( error ) => {
8142- logger . warn ( 'Diff viewer auto-start failed' , { error : error . message } ) ;
8143- } ) ;
8144- }
8145-
8146- // Initialize sessions
8147- const shouldAutoEnsureDiscordServices = ( ( ) => {
8148- const envRaw = String ( process . env . DISCORD_AUTO_ENSURE_SERVICES ?? '' ) . trim ( ) . toLowerCase ( ) ;
8149- if ( envRaw ) return ! [ '0' , 'false' , 'no' ] . includes ( envRaw ) ;
8133+ if ( ! hostPolicy . isLoopback ) {
8134+ const bindType = hostPolicy . isBindAll ? 'bind-all' : 'explicit-host' ;
8135+ logger . info ( `LAN access enabled (${ bindType } ) on port ${ port } ` ) ;
8136+ if ( ! hostPolicy . hasAuthToken ) {
8137+ logger . warn ( 'LAN access is enabled without AUTH_TOKEN. This is insecure; anyone on the network can control this orchestrator.' , { host : HOST , port } ) ;
8138+ }
8139+ }
8140+ if ( hostPolicy . hasAuthToken ) {
8141+ logger . info ( 'Authentication enabled' ) ;
8142+ }
81508143
8151- try {
8152- const cfg = userSettingsService ?. settings ?. global ?. ui ?. discord || { } ;
8153- return cfg . autoEnsureServicesAtStartup === true ;
8154- } catch {
8155- return false ;
8144+ // Start the Advanced Diff Viewer in the background.
8145+ // Default: enabled, since users expect the diff viewer to be ready without manual terminal steps.
8146+ const autoStartRaw = String ( process . env . AUTO_START_DIFF_VIEWER ?? 'true' ) . toLowerCase ( ) ;
8147+ const shouldAutoStartDiffViewer = ! [ '0' , 'false' , 'no' ] . includes ( autoStartRaw ) ;
8148+ if ( shouldAutoStartDiffViewer ) {
8149+ diffViewerService . ensureRunning ( ) . catch ( ( error ) => {
8150+ logger . warn ( 'Diff viewer auto-start failed' , { error : error . message } ) ;
8151+ } ) ;
81568152 }
8157- } ) ( ) ;
81588153
8159- workspaceSystemReady
8160- . then ( ( workspaceReady ) => {
8161- if ( ! workspaceReady ) {
8162- return ;
8154+ // Initialize sessions
8155+ const shouldAutoEnsureDiscordServices = ( ( ) => {
8156+ const envRaw = String ( process . env . DISCORD_AUTO_ENSURE_SERVICES ?? '' ) . trim ( ) . toLowerCase ( ) ;
8157+ if ( envRaw ) return ! [ '0' , 'false' , 'no' ] . includes ( envRaw ) ;
8158+
8159+ try {
8160+ const cfg = userSettingsService ?. settings ?. global ?. ui ?. discord || { } ;
8161+ return cfg . autoEnsureServicesAtStartup === true ;
8162+ } catch {
8163+ return false ;
81638164 }
8164- return sessionManager . initializeSessions ( ) ;
8165- } )
8166- . then ( ( ) => {
8167- if ( ! shouldAutoEnsureDiscordServices ) return ;
8168- // Don’t block server startup; just best-effort keep Services running after restarts.
8169- return discordIntegrationService . ensureDiscordServices ( { sessionManager, workspaceManager } )
8170- . then ( ( ) => logger . info ( 'Discord services ensured on startup' ) )
8171- . catch ( ( error ) => logger . warn ( 'Failed to ensure Discord services on startup' , { error : error . message } ) ) ;
8172- } )
8173- . catch ( ( error ) => {
8174- logger . error ( 'Failed to initialize sessions' , { error : error . message , stack : error . stack } ) ;
8175- } ) ;
8176- } ) ;
8165+ } ) ( ) ;
8166+
8167+ workspaceSystemReady
8168+ . then ( ( workspaceReady ) => {
8169+ if ( ! workspaceReady ) {
8170+ return ;
8171+ }
8172+ return sessionManager . initializeSessions ( ) ;
8173+ } )
8174+ . then ( ( ) => {
8175+ if ( ! shouldAutoEnsureDiscordServices ) return ;
8176+ // Don't block server startup; just best-effort keep Services running after restarts.
8177+ return discordIntegrationService . ensureDiscordServices ( { sessionManager, workspaceManager } )
8178+ . then ( ( ) => logger . info ( 'Discord services ensured on startup' ) )
8179+ . catch ( ( error ) => logger . warn ( 'Failed to ensure Discord services on startup' , { error : error . message } ) ) ;
8180+ } )
8181+ . catch ( ( error ) => {
8182+ logger . error ( 'Failed to initialize sessions' , { error : error . message , stack : error . stack } ) ;
8183+ } ) ;
8184+ } ) ;
8185+
8186+ httpServer . once ( 'error' , ( err ) => {
8187+ if ( err . code === 'EADDRINUSE' && attempt < MAX_PORT_ATTEMPTS ) {
8188+ logger . warn ( `Port ${ port } in use, trying ${ port + 1 } ...` ) ;
8189+ httpServer . close ( ) ;
8190+ tryListen ( port + 1 , attempt + 1 ) ;
8191+ } else {
8192+ logger . error ( 'Failed to start server' , { error : err . message , port, attempts : attempt } ) ;
8193+ process . exit ( 1 ) ;
8194+ }
8195+ } ) ;
8196+ }
8197+
8198+ tryListen ( DESIRED_PORT , 1 ) ;
81778199
81788200// Graceful shutdown
81798201process . on ( 'SIGTERM' , ( ) => shutdown ( 'SIGTERM' ) ) ;
0 commit comments