@@ -124,7 +124,12 @@ import {
124124 resolveExecutorDataDir ,
125125 writeLocalServerManifest ,
126126} from "./local-server-manifest" ;
127- import { DEFAULT_SERVICE_PORT , getServiceBackend , SERVICE_LABEL } from "./service" ;
127+ import {
128+ DEFAULT_SERVICE_PORT ,
129+ getServiceBackend ,
130+ SERVICE_LABEL ,
131+ stopWindowsExecutorListenersOnPort ,
132+ } from "./service" ;
128133import {
129134 defaultCliServerConnectionProfile ,
130135 findCliServerConnectionProfile ,
@@ -165,6 +170,7 @@ const DEFAULT_BASE_URL = `http://localhost:${DEFAULT_PORT}`;
165170const DAEMON_BOOT_TIMEOUT_MS = 15_000 ;
166171const DAEMON_BOOT_POLL_MS = 150 ;
167172const DAEMON_STOP_TIMEOUT_MS = 10_000 ;
173+ const SERVICE_BOOT_TIMEOUT_MS = 45_000 ;
168174
169175// ---------------------------------------------------------------------------
170176// Helpers
@@ -197,15 +203,15 @@ const readActiveLocalServerManifest = (): Effect.Effect<
197203 const manifest = yield * readLocalServerManifest ( ) ;
198204 if ( ! manifest ) return null ;
199205
206+ if ( yield * isServerReachable ( manifest . connection . origin ) ) {
207+ return manifest ;
208+ }
209+
200210 if ( ! isPidAlive ( manifest . pid ) ) {
201211 yield * removeLocalServerManifestIfOwnedBy ( { pid : manifest . pid } ) . pipe ( Effect . ignore ) ;
202212 return null ;
203213 }
204214
205- if ( yield * isServerReachable ( manifest . connection . origin ) ) {
206- return manifest ;
207- }
208-
209215 return yield * Effect . fail (
210216 new Error (
211217 [
@@ -2201,6 +2207,68 @@ const mcpCommand = Command.make(
22012207
22022208const supervisedServiceOrigin = ( port : number ) : string => `http://127.0.0.1:${ port } ` ;
22032209
2210+ const LOCAL_SERVICE_HOSTS = new Set ( [ "localhost" , "127.0.0.1" , "::1" , "[::1]" ] ) ;
2211+
2212+ const originPort = ( url : URL ) : string => url . port || ( url . protocol === "https:" ? "443" : "80" ) ;
2213+
2214+ const sameServerOrigin = ( left : string , right : string ) : boolean => {
2215+ const leftUrl = new URL ( normalizeExecutorServerConnection ( { origin : left } ) . origin ) ;
2216+ const rightUrl = new URL ( normalizeExecutorServerConnection ( { origin : right } ) . origin ) ;
2217+ if ( leftUrl . toString ( ) === rightUrl . toString ( ) ) return true ;
2218+ return (
2219+ leftUrl . protocol === rightUrl . protocol &&
2220+ originPort ( leftUrl ) === originPort ( rightUrl ) &&
2221+ LOCAL_SERVICE_HOSTS . has ( leftUrl . hostname . toLowerCase ( ) ) &&
2222+ LOCAL_SERVICE_HOSTS . has ( rightUrl . hostname . toLowerCase ( ) )
2223+ ) ;
2224+ } ;
2225+
2226+ const hasReachableCliDaemonManifest = ( input : {
2227+ readonly origin : string ;
2228+ readonly version : string ;
2229+ } ) : Effect . Effect < boolean , never , FileSystem . FileSystem | PlatformPath . Path > =>
2230+ Effect . gen ( function * ( ) {
2231+ const manifest = yield * readActiveLocalServerManifest ( ) . pipe (
2232+ Effect . catchCause ( ( ) => Effect . succeed ( null ) ) ,
2233+ ) ;
2234+ if ( ! manifest || manifest . kind !== "cli-daemon" ) return false ;
2235+ if ( ! sameServerOrigin ( manifest . connection . origin , input . origin ) ) return false ;
2236+ if ( manifest . owner . version !== input . version ) return false ;
2237+ return yield * isServerReachable ( manifest . connection . origin ) ;
2238+ } ) ;
2239+
2240+ const clearUnmanifestedWindowsExecutorListener = ( input : {
2241+ readonly port : number ;
2242+ readonly origin : string ;
2243+ } ) : Effect . Effect < void , Error , FileSystem . FileSystem | PlatformPath . Path > =>
2244+ Effect . gen ( function * ( ) {
2245+ const active = yield * readActiveLocalServerManifest ( ) . pipe (
2246+ Effect . catchCause ( ( ) => Effect . succeed ( null ) ) ,
2247+ ) ;
2248+ if ( active && sameServerOrigin ( active . connection . origin , input . origin ) ) return ;
2249+ if ( ! ( yield * isServerReachable ( input . origin ) ) ) return ;
2250+
2251+ const stoppedPids = yield * stopWindowsExecutorListenersOnPort ( input . port ) ;
2252+ const stopped = yield * waitForUnreachable ( {
2253+ check : isServerReachable ( input . origin ) ,
2254+ timeoutMs : DAEMON_STOP_TIMEOUT_MS ,
2255+ intervalMs : DAEMON_BOOT_POLL_MS ,
2256+ } ) ;
2257+ if ( stopped ) return ;
2258+
2259+ return yield * Effect . fail (
2260+ new Error (
2261+ [
2262+ `Executor is already reachable at ${ input . origin } , but no live server manifest exists for it.` ,
2263+ stoppedPids . length > 0
2264+ ? `Tried to stop orphaned Executor pid(s): ${ stoppedPids . join ( ", " ) } .`
2265+ : `No orphaned executor.exe listener could be stopped on port ${ input . port } .` ,
2266+ "Stop the process using that port and re-run the install." ,
2267+ ] . join ( "\n" ) ,
2268+ ) ,
2269+ ) ;
2270+ } ) ;
2271+
22042272const portFromOrigin = ( origin : string ) : number | null => {
22052273 try {
22062274 const url = new URL ( origin ) ;
@@ -2274,7 +2342,10 @@ const installService = (port: number, commandName: string) =>
22742342 return ;
22752343 }
22762344
2277- if ( plan === "takeover-then-install" ) {
2345+ if (
2346+ plan === "takeover-then-install" ||
2347+ ( backend . platform === "win32" && plan === "reinstall" )
2348+ ) {
22782349 const replaced = yield * takeOverActiveLocalServer ( ) ;
22792350 if ( replaced ) {
22802351 console . log (
@@ -2294,22 +2365,29 @@ const installService = (port: number, commandName: string) =>
22942365 console . log ( "" ) ;
22952366 console . log ( "Writing the service definition and starting Executor..." ) ;
22962367
2368+ if ( backend . platform === "win32" ) {
2369+ yield * clearUnmanifestedWindowsExecutorListener ( { port, origin } ) ;
2370+ }
2371+
22972372 // The unit carries no secret: the supervised daemon mints/loads its bearer
22982373 // from auth.json (under EXECUTOR_DATA_DIR) on first boot, and clients read
22992374 // the same file — so reachability is the credential-free /api/health probe.
23002375 yield * backend . install ( { executablePath : process . execPath , port, version : CLI_VERSION } ) ;
23012376
2302- console . log ( `Waiting for Executor to become reachable at ${ origin } ...` ) ;
2377+ console . log ( `Waiting for Executor to publish its service manifest at ${ origin } ...` ) ;
23032378 const reachable = yield * waitForReachable ( {
2304- check : isServerReachable ( origin ) ,
2305- timeoutMs : DAEMON_BOOT_TIMEOUT_MS ,
2379+ check : hasReachableCliDaemonManifest ( {
2380+ origin,
2381+ version : CLI_VERSION ,
2382+ } ) ,
2383+ timeoutMs : SERVICE_BOOT_TIMEOUT_MS ,
23062384 intervalMs : DAEMON_BOOT_POLL_MS ,
23072385 } ) ;
23082386 if ( ! reachable ) {
23092387 return yield * Effect . fail (
23102388 new Error (
23112389 [
2312- `Installed ${ SERVICE_LABEL } but it did not become reachable at ${ origin } within ${ DAEMON_BOOT_TIMEOUT_MS / 1000 } s.` ,
2390+ `Installed ${ SERVICE_LABEL } but it did not publish a reachable server manifest for ${ origin } within ${ SERVICE_BOOT_TIMEOUT_MS / 1000 } s.` ,
23132391 `Check ~/.executor/logs/daemon.error.log and \`${ cliPrefix } service status\`.` ,
23142392 ] . join ( "\n" ) ,
23152393 ) ,
@@ -2334,19 +2412,25 @@ const serviceInstallCommand = Command.make(
23342412const serviceUninstallCommand = Command . make ( "uninstall" , { } , ( ) =>
23352413 Effect . gen ( function * ( ) {
23362414 const backend = getServiceBackend ( ) ;
2337- const wasRunning = backend . automated
2338- ? yield * backend . status ( ) . pipe (
2339- Effect . map ( ( status ) => status . running ) ,
2340- Effect . catchCause ( ( ) => Effect . succeed ( false ) ) ,
2341- )
2342- : false ;
2415+ const activeBefore = yield * readActiveLocalServerManifest ( ) . pipe (
2416+ Effect . catchCause ( ( ) => Effect . succeed ( null ) ) ,
2417+ ) ;
2418+ const servicePort = activeBefore
2419+ ? ( portFromOrigin ( activeBefore . connection . origin ) ?? DEFAULT_SERVICE_PORT )
2420+ : DEFAULT_SERVICE_PORT ;
2421+ const status = backend . automated
2422+ ? yield * backend . status ( ) . pipe ( Effect . catchCause ( ( ) => Effect . succeed ( null ) ) )
2423+ : null ;
23432424 yield * backend . uninstall ( ) ;
2344- if ( wasRunning ) {
2345- const stopped = yield * takeOverActiveLocalServer ( { onlyKind : "cli-daemon" } ) ;
2346- if ( stopped ) {
2347- console . log (
2348- `Stopped running Executor daemon at ${ stopped . connection . origin } (pid ${ stopped . pid } ).` ,
2349- ) ;
2425+ const stopped = yield * takeOverActiveLocalServer ( { onlyKind : "cli-daemon" } ) ;
2426+ if ( stopped ) {
2427+ console . log (
2428+ `Stopped running Executor daemon at ${ stopped . connection . origin } (pid ${ stopped . pid } ).` ,
2429+ ) ;
2430+ } else if ( backend . platform === "win32" && status ?. registered ) {
2431+ const stoppedPids = yield * stopWindowsExecutorListenersOnPort ( servicePort ) ;
2432+ if ( stoppedPids . length > 0 ) {
2433+ console . log ( `Stopped orphaned Executor daemon pid(s): ${ stoppedPids . join ( ", " ) } .` ) ;
23502434 }
23512435 }
23522436 console . log ( "Executor background service uninstalled." ) ;
0 commit comments