11import { randomUUID } from "node:crypto" ;
22import type { WorkspaceMode , WorkspaceStore } from "./workspace-store.js" ;
3- import { mkdir , opendir , stat } from "node:fs/promises" ;
3+ import { mkdir , opendir , readFile , realpath , stat } from "node:fs/promises" ;
44import { dirname , join , relative , resolve , sep } from "node:path" ;
55import { loadProjectContextFiles } from "@earendil-works/pi-coding-agent" ;
66import type { ServerConfig } from "./config.js" ;
@@ -220,7 +220,7 @@ export class WorkspaceRegistry {
220220 managed : workspace . worktree ?. managed ,
221221 } ) ;
222222 this . workspaces . set ( workspace . id , workspace ) ;
223- const agentsFiles = this . loadInitialAgentsFiles ( workspace . root ) ;
223+ const agentsFiles = await this . loadInitialAgentsFiles ( workspace . root ) ;
224224 const availableAgentsFiles = await this . findAvailableAgentsFiles ( workspace . root , agentsFiles ) ;
225225
226226 return { workspace, agentsFiles, availableAgentsFiles } ;
@@ -246,32 +246,43 @@ export class WorkspaceRegistry {
246246 return assertAllowedPath ( root , this . config . allowedRoots ) ;
247247 }
248248
249- private loadInitialAgentsFiles ( root : string ) : LoadedAgentsFile [ ] {
249+ private async loadInitialAgentsFiles ( root : string ) : Promise < LoadedAgentsFile [ ] > {
250250 const agentDir = resolve ( this . config . agentDir ) ;
251+ const loadedFiles : LoadedAgentsFile [ ] = [ ] ;
252+
253+ for ( const file of loadProjectContextFiles ( { cwd : root , agentDir } ) ) {
254+ const path = resolve ( file . path ) ;
255+ if ( ! isInitialAgentsFilePath ( path , root , agentDir ) ) continue ;
256+ const content = await readResolvedContextFile ( path , file . content , root , agentDir ) ;
257+ if ( content === undefined ) continue ;
258+
259+ loadedFiles . push ( {
260+ path,
261+ content,
262+ } ) ;
263+ }
251264
252- return loadProjectContextFiles ( { cwd : root , agentDir } )
253- . filter ( ( file ) => {
254- const path = resolve ( file . path ) ;
255- if ( isPathInsideRoot ( path , agentDir ) ) return true ;
256- return isPathInsideRoot ( path , root ) && dirname ( path ) === root ;
257- } )
258- . map ( ( file ) => ( {
259- path : resolve ( file . path ) ,
260- content : file . content ,
261- } ) ) ;
265+ return loadedFiles ;
262266 }
263267
264268 private async findAvailableAgentsFiles (
265269 root : string ,
266270 loadedFiles : LoadedAgentsFile [ ] ,
267271 ) : Promise < AvailableAgentsFile [ ] > {
268272 const loadedPaths = new Set ( loadedFiles . map ( ( file ) => resolve ( file . path ) ) ) ;
273+ const loadedRealPaths = new Set < string > ( ) ;
274+ for ( const file of loadedFiles ) {
275+ const realPath = await tryRealpath ( file . path ) ;
276+ if ( realPath ) loadedRealPaths . add ( realPath ) ;
277+ }
269278 const discovered : AvailableAgentsFile [ ] = [ ] ;
270279
271280 await walkWorkspace ( root , async ( path , entry ) => {
272281 if ( ! entry . isFile ( ) ) return ;
273282 if ( ! CONTEXT_FILE_NAMES . has ( entry . name ) ) return ;
274283 if ( loadedPaths . has ( path ) ) return ;
284+ const realPath = await tryRealpath ( path ) ;
285+ if ( realPath && loadedRealPaths . has ( realPath ) ) return ;
275286
276287 discovered . push ( { path } ) ;
277288 } ) ;
@@ -310,6 +321,34 @@ export function formatAgentsPath(path: string, workspaceRoot: string | undefined
310321 return relationship . split ( sep ) . join ( "/" ) ;
311322}
312323
324+ function isInitialAgentsFilePath ( path : string , root : string , agentDir : string ) : boolean {
325+ if ( isPathInsideRoot ( path , agentDir ) ) return true ;
326+ return isPathInsideRoot ( path , root ) && dirname ( path ) === root ;
327+ }
328+
329+ async function readResolvedContextFile (
330+ path : string ,
331+ fallbackContent : string ,
332+ root : string ,
333+ agentDir : string ,
334+ ) : Promise < string | undefined > {
335+ try {
336+ const resolvedPath = await realpath ( path ) ;
337+ if ( ! isInitialAgentsFilePath ( resolvedPath , root , agentDir ) ) return undefined ;
338+ return await readFile ( resolvedPath , "utf8" ) ;
339+ } catch {
340+ return fallbackContent ;
341+ }
342+ }
343+
344+ async function tryRealpath ( path : string ) : Promise < string | undefined > {
345+ try {
346+ return await realpath ( path ) ;
347+ } catch {
348+ return undefined ;
349+ }
350+ }
351+
313352async function walkWorkspace (
314353 directory : string ,
315354 visit : ( path : string , entry : { name : string ; isFile ( ) : boolean ; isDirectory ( ) : boolean } ) => Promise < void > | void ,
0 commit comments