@@ -25,14 +25,28 @@ export function createForgeSessionAttachHook(deps: ForgeSessionAttachHookDeps) {
2525 const sessionInfo = eventInput . event . properties ?. info as Record < string , unknown > | undefined
2626 const sessionId = sessionInfo ?. id as string | undefined
2727 const workspaceId = sessionInfo ?. workspaceID as string | undefined
28+ const sessionDirectory = sessionInfo ?. directory as string | undefined
29+ const sessionProjectId = ( sessionInfo ?. projectID as string | undefined ) ?? deps . projectId
2830 if ( ! sessionId || ! workspaceId ) return
2931
30- let ws = await findWorkspaceById ( deps , workspaceId )
32+ let ws = await findWorkspaceById ( deps , workspaceId , sessionDirectory )
3133 if ( ! ws ) {
3234 await new Promise < void > ( ( r ) => setTimeout ( r , 100 ) )
33- ws = await findWorkspaceById ( deps , workspaceId )
35+ ws = await findWorkspaceById ( deps , workspaceId , sessionDirectory )
3436 if ( ! ws ) {
35- deps . logger . log ( `[forge-session-attach] skip session=${ sessionId } : workspace ${ workspaceId } not found via experimental.workspace.list (may be cross-project; check plugin directory)` )
37+ deps . logger . log (
38+ `[forge-session-attach] skip session=${ sessionId } : workspace ${ workspaceId } not found ` +
39+ `via experimental.workspace.list directory=${ sessionDirectory ?? '(none)' } ` +
40+ `(cross-project or sync lag)` ,
41+ )
42+ if ( sessionDirectory ) {
43+ publishAttachFailureToast (
44+ deps ,
45+ sessionDirectory ,
46+ `Forge loop (workspace ${ workspaceId } )` ,
47+ 'Workspace not visible from this plugin instance - open the TUI in the loop\'s project, or run the reconciler.' ,
48+ )
49+ }
3650 return
3751 }
3852 }
@@ -61,16 +75,16 @@ export function createForgeSessionAttachHook(deps: ForgeSessionAttachHookDeps) {
6175 return
6276 }
6377
64- const existing = deps . execDeps . loopsRepo . get ( deps . projectId , cfg . loopName )
78+ const existing = deps . execDeps . loopsRepo . get ( sessionProjectId , cfg . loopName )
6579 if ( existing && existing . status === 'running' ) {
6680 // Live loop with this name; skip to avoid double-attach.
6781 deps . logger . log ( `[forge-session-attach] skip session=${ sessionId } loop=${ cfg . loopName } reason=already-running` )
6882 return
6983 }
7084 if ( existing ) {
71- deps . logger . log ( `[forge-session-attach] session=${ sessionId } loop=${ cfg . loopName } existing-row-status=${ existing . status } (will re-attach)` )
85+ deps . logger . log ( `[forge-session-attach] session=${ sessionId } loop=${ cfg . loopName } projectId= ${ sessionProjectId } existing-row-status=${ existing . status } (will re-attach)` )
7286 } else {
73- deps . logger . log ( `[forge-session-attach] session=${ sessionId } loop=${ cfg . loopName } no existing row, proceeding` )
87+ deps . logger . log ( `[forge-session-attach] session=${ sessionId } loop=${ cfg . loopName } projectId= ${ sessionProjectId } no existing row, proceeding` )
7488 }
7589
7690 const resolvedHostSessionId = cfg . hostSessionId && cfg . hostSessionId . length > 0
@@ -86,7 +100,7 @@ export function createForgeSessionAttachHook(deps: ForgeSessionAttachHookDeps) {
86100 if ( planSource . kind === 'inline' ) {
87101 planText = planSource . planText
88102 } else {
89- const row = deps . execDeps . plansRepo . getForSession ( deps . projectId , planSource . sessionId )
103+ const row = deps . execDeps . plansRepo . getForSession ( sessionProjectId , planSource . sessionId )
90104 if ( ! row ) {
91105 deps . logger . error ( `[forge-session-attach] plan not found for session=${ planSource . sessionId } loop=${ cfg . loopName } workspace=${ workspaceId } ` )
92106 await failAndCleanup (
@@ -104,7 +118,7 @@ export function createForgeSessionAttachHook(deps: ForgeSessionAttachHookDeps) {
104118 try {
105119 const result = await attachLoopToSession (
106120 deps . execDeps ,
107- { surface : 'tui' , projectId : deps . projectId , directory : ws . directory ?? deps . directory } ,
121+ { surface : 'tui' , projectId : sessionProjectId , directory : ws . directory ?? deps . directory } ,
108122 {
109123 sessionId,
110124 workspaceId,
@@ -153,14 +167,14 @@ async function failAndCleanup(
153167 loopName : string ,
154168 message : string ,
155169) : Promise < void > {
156- publishAttachFailureToast ( deps , directory , loopName , message )
170+ publishAttachFailureToast ( deps , directory , `Forge loop " ${ loopName } "` , message )
157171 await removeOrphanWorkspace ( deps , workspaceId , loopName )
158172}
159173
160174function publishAttachFailureToast (
161175 deps : ForgeSessionAttachHookDeps ,
162176 directory : string ,
163- loopName : string ,
177+ title : string ,
164178 message : string ,
165179) : void {
166180 const tui = deps . v2 . tui
@@ -169,12 +183,7 @@ function publishAttachFailureToast(
169183 directory,
170184 body : {
171185 type : 'tui.toast.show' ,
172- properties : {
173- title : `Forge loop "${ loopName } "` ,
174- message,
175- variant : 'error' ,
176- duration : 6000 ,
177- } ,
186+ properties : { title, message, variant : 'error' , duration : 6000 } ,
178187 } ,
179188 } ) . catch ( ( err ) => {
180189 deps . logger . error ( '[forge-session-attach] failed to publish toast' , err )
@@ -206,9 +215,12 @@ async function removeOrphanWorkspace(
206215async function findWorkspaceById (
207216 deps : ForgeSessionAttachHookDeps ,
208217 workspaceId : string ,
218+ directory ?: string ,
209219) : Promise < WorkspaceEntry | null > {
210220 try {
211- const result = await deps . v2 . experimental . workspace . list ( )
221+ const result = await deps . v2 . experimental . workspace . list (
222+ directory ? { directory } : undefined ,
223+ )
212224 const entries = ( result . data ?? [ ] ) as WorkspaceEntry [ ]
213225 return entries . find ( ( e ) => e . id === workspaceId ) ?? null
214226 } catch {
0 commit comments