1+ import fs from "node:fs" ;
2+ import path from "node:path" ;
13import { query as claudeQuery , type SDKMessage } from "@anthropic-ai/claude-agent-sdk" ;
24import type { Logger } from "../logging/logger" ;
35import { getErrorMessage } from "../shared/utils" ;
6+ import { resolveAdeMcpServerLaunch } from "../orchestrator/unifiedOrchestratorAdapter" ;
47import {
58 reportProviderRuntimeAuthFailure ,
69 reportProviderRuntimeFailure ,
710 reportProviderRuntimeReady ,
811} from "./providerRuntimeHealth" ;
12+ import { resolveClaudeCodeExecutable } from "./claudeCodeExecutable" ;
13+ import { normalizeCliMcpServers } from "./providerResolver" ;
914
1015const PROBE_TIMEOUT_MS = 20_000 ;
1116const PROBE_CACHE_TTL_MS = 30_000 ;
@@ -22,6 +27,7 @@ type ClaudeRuntimeProbeResult =
2227/** Cache and in-flight probe keyed by projectRoot to avoid cross-project contamination. */
2328const probeCache = new Map < string , { checkedAtMs : number ; result : ClaudeRuntimeProbeResult } > ( ) ;
2429const inFlightProbes = new Map < string , Promise < ClaudeRuntimeProbeResult > > ( ) ;
30+ let runtimeRootCache : string | null = null ;
2531
2632function normalizeErrorMessage ( error : unknown ) : string {
2733 const text = getErrorMessage ( error ) . trim ( ) ;
@@ -84,16 +90,52 @@ function cacheResult(projectRoot: string, result: ClaudeRuntimeProbeResult): Cla
8490 return result ;
8591}
8692
87- function publishResult ( result : ClaudeRuntimeProbeResult ) : void {
88- if ( result . state === "ready" ) {
89- reportProviderRuntimeReady ( "claude" ) ;
90- return ;
93+ function resolveProbeRuntimeRoot ( ) : string {
94+ if ( runtimeRootCache !== null ) return runtimeRootCache ;
95+ const startPoints = [ process . cwd ( ) , __dirname ] ;
96+ for ( const start of startPoints ) {
97+ let dir = path . resolve ( start ) ;
98+ for ( let i = 0 ; i < 12 ; i += 1 ) {
99+ if ( fs . existsSync ( path . join ( dir , "apps" , "mcp-server" , "package.json" ) ) ) {
100+ runtimeRootCache = dir ;
101+ return dir ;
102+ }
103+ const parent = path . dirname ( dir ) ;
104+ if ( parent === dir ) break ;
105+ dir = parent ;
106+ }
91107 }
92- if ( result . state === "auth-failed" ) {
93- reportProviderRuntimeAuthFailure ( "claude" , result . message ) ;
94- return ;
108+ runtimeRootCache = process . cwd ( ) ;
109+ return runtimeRootCache ;
110+ }
111+
112+ function resolveProbeMcpServers ( projectRoot : string ) : Record < string , Record < string , unknown > > | undefined {
113+ const launch = resolveAdeMcpServerLaunch ( {
114+ workspaceRoot : projectRoot ,
115+ runtimeRoot : resolveProbeRuntimeRoot ( ) ,
116+ defaultRole : "agent" ,
117+ } ) ;
118+ return normalizeCliMcpServers ( "claude" , {
119+ ade : {
120+ command : launch . command ,
121+ args : launch . cmdArgs ,
122+ env : launch . env ,
123+ } ,
124+ } ) ;
125+ }
126+
127+ function publishResult ( result : ClaudeRuntimeProbeResult ) : void {
128+ switch ( result . state ) {
129+ case "ready" :
130+ reportProviderRuntimeReady ( "claude" ) ;
131+ break ;
132+ case "auth-failed" :
133+ reportProviderRuntimeAuthFailure ( "claude" , result . message ) ;
134+ break ;
135+ case "runtime-failed" :
136+ reportProviderRuntimeFailure ( "claude" , result . message ) ;
137+ break ;
95138 }
96- reportProviderRuntimeFailure ( "claude" , result . message ) ;
97139}
98140
99141export function resetClaudeRuntimeProbeCache ( ) : void {
@@ -119,20 +161,28 @@ export async function probeClaudeRuntimeHealth(args: {
119161 return ;
120162 }
121163
164+ let claudeExecutablePath : string | null = null ;
165+
122166 const probe = ( async ( ) : Promise < ClaudeRuntimeProbeResult > => {
123167 const abortController = new AbortController ( ) ;
124168 const timeout = setTimeout ( ( ) => abortController . abort ( ) , PROBE_TIMEOUT_MS ) ;
125- const stream = claudeQuery ( {
126- prompt : "System initialization check. Respond with only the word READY." ,
127- options : {
128- cwd : projectRoot ,
129- permissionMode : "plan" ,
130- tools : [ ] ,
131- abortController,
132- } ,
133- } ) ;
169+ let stream : ReturnType < typeof claudeQuery > | null = null ;
134170
135171 try {
172+ const claudeExecutable = resolveClaudeCodeExecutable ( ) ;
173+ claudeExecutablePath = claudeExecutable . path ;
174+ stream = claudeQuery ( {
175+ prompt : "System initialization check. Respond with only the word READY." ,
176+ options : {
177+ cwd : projectRoot ,
178+ permissionMode : "plan" ,
179+ tools : [ ] ,
180+ pathToClaudeCodeExecutable : claudeExecutable . path ,
181+ mcpServers : resolveProbeMcpServers ( projectRoot ) as any ,
182+ abortController,
183+ } ,
184+ } ) ;
185+
136186 for await ( const message of stream ) {
137187 const result = resultFromSdkMessage ( message ) ;
138188 if ( result ) {
@@ -154,7 +204,7 @@ export async function probeClaudeRuntimeHealth(args: {
154204 } finally {
155205 clearTimeout ( timeout ) ;
156206 try {
157- stream . close ( ) ;
207+ stream ? .close ( ) ;
158208 } catch {
159209 // Best effort cleanup — avoid leaving the probe subprocess running.
160210 }
@@ -172,6 +222,7 @@ export async function probeClaudeRuntimeHealth(args: {
172222 projectRoot,
173223 state : result . state ,
174224 message : result . message ,
225+ claudeExecutablePath,
175226 } ) ;
176227 }
177228 } finally {
0 commit comments