@@ -17,6 +17,7 @@ import { cleanupSession, resolveForwardingExecutor } from '../hooks/lib/hook-sta
1717
1818const root = fileURLToPath ( new URL ( '../' , import . meta. url ) ) ;
1919const fakeZCode = join ( root , 'tests/fixtures/fake-zcode-cli.mjs' ) ;
20+ const socketMethodRecorder = new URL ( './fixtures/record-socket-methods.mjs' , import . meta. url ) . href ;
2021const legacyBroker = join ( root , 'tests/fixtures/legacy-zcode-broker-v1.mjs' ) ;
2122const ownerStoreLockHolder = join ( root , 'tests/fixtures/owner-store-lock-holder.mjs' ) ;
2223// Parallel Windows runners can spend more than 750 ms scheduling a legacy
@@ -277,7 +278,7 @@ test('SessionEnd removes only its session contexts and leaves sibling jobs/sessi
277278} ) ;
278279
279280test ( 'SessionEnd releases only its broker owner sessions and lets the idle broker exit' , async ( ) => {
280- const { cwd, data, env } = await workspace ( ) ; const record = join ( data , 'zcode-calls.jsonl' ) ; await writeFile ( record , '' ) ;
281+ const { cwd, data, env } = await workspace ( ) ; const record = join ( data , 'zcode-calls.jsonl' ) ; const socketMethods = join ( data , 'hook-socket-methods.txt' ) ; await writeFile ( record , '' ) ; await writeFile ( socketMethods , '' ) ;
281282 const launch = { command : process . execPath , args : [ fakeZCode ] , target : fakeZCode } ; const clients = [ ] ;
282283 for ( const sessionId of [ 'a' , 'b' ] ) {
283284 await runHook ( 'session-lifecycle-hook.mjs' , { session_id : sessionId , cwd, hook_event_name : 'SessionStart' , transcript_path : null , model : 'gpt' , permission_mode : 'default' , source : 'startup' } , env ) ;
@@ -286,12 +287,13 @@ test('SessionEnd releases only its broker owner sessions and lets the idle broke
286287 }
287288 for ( const client of clients ) await client . close ( ) ;
288289 const storage = await resolveWorkspaceStorage ( { dataRoot : data , workspace : cwd } ) ; const identity = JSON . parse ( await readFile ( join ( storage . directory , 'broker/identity.json' ) , 'utf8' ) ) ; const ownershipPath = join ( storage . directory , 'broker/session-owners.json' ) ; const ownershipBefore = await stat ( ownershipPath ) ; const hookStartedAt = Date . now ( ) ;
289- const ended = await runHook ( 'session-end-hook.mjs' , { session_id : 'a' , cwd, hook_event_name : 'SessionEnd' , transcript_path : null , reason : 'other' } , env ) ; const hookElapsedMs = Date . now ( ) - hookStartedAt ;
290+ const ended = await runHook ( 'session-end-hook.mjs' , { session_id : 'a' , cwd, hook_event_name : 'SessionEnd' , transcript_path : null , reason : 'other' } , { ... env , NODE_OPTIONS : ` ${ process . env . NODE_OPTIONS ?? '' } --import= ${ socketMethodRecorder } ` . trim ( ) , ZCODE_TEST_SOCKET_METHOD_RECORD : socketMethods } ) ; const hookElapsedMs = Date . now ( ) - hookStartedAt ;
290291 assert . equal ( ended . code , 0 ) ;
291292 const owners = JSON . parse ( await readFile ( ownershipPath , 'utf8' ) ) ;
292293 let releaseDiagnostic = 'owner release succeeded without diagnostic collection' ;
293- if ( JSON . stringify ( owners . sessions ) !== JSON . stringify ( { 'zcode-b' : ownerIdForSession ( 'b' ) } ) ) { const callsAtFailure = ( await readFile ( record , 'utf8' ) ) . trim ( ) . split ( '\n' ) . filter ( Boolean ) . map ( ( line ) => JSON . parse ( line ) ) ; const ownershipAfter = await stat ( ownershipPath ) ; const healthyAfterFailure = await probeBrokerHealth ( identity , 250 ) ; const childPidProbe = await probePidFromChild ( identity . pid ) ; let ownedJobsProbe ; try { ownedJobsProbe = { count : ( await createStateStore ( { dataRoot : data } ) . listOwnedJobs ( cwd , 'a' ) ) . length } ; } catch ( error ) { ownedJobsProbe = { error : { code : error ?. code ?? null , category : error ?. category ?? null , details : error ?. details ?? null } } ; } releaseDiagnostic = `release-stage ${ JSON . stringify ( { hookElapsedMs, stopObserved : callsAtFailure . some ( ( call ) => call . method === 'session/stop' && call . params ?. sessionId === 'zcode-a' ) , ownerStoreReplaced : ownershipAfter . ino !== ownershipBefore . ino || ownershipAfter . mtimeMs !== ownershipBefore . mtimeMs , healthyAfterFailure, childPidProbe, ownedJobsProbe, hookCode : ended . code , hookDiagnostic : ended . stderr . trim ( ) || null } ) } ` ; }
294+ if ( JSON . stringify ( owners . sessions ) !== JSON . stringify ( { 'zcode-b' : ownerIdForSession ( 'b' ) } ) ) { const callsAtFailure = ( await readFile ( record , 'utf8' ) ) . trim ( ) . split ( '\n' ) . filter ( Boolean ) . map ( ( line ) => JSON . parse ( line ) ) ; const ownershipAfter = await stat ( ownershipPath ) ; const healthyAfterFailure = await probeBrokerHealth ( identity , 250 ) ; const childPidProbe = await probePidFromChild ( identity . pid ) ; let ownedJobsProbe ; try { ownedJobsProbe = { count : ( await createStateStore ( { dataRoot : data } ) . listOwnedJobs ( cwd , 'a' ) ) . length } ; } catch ( error ) { ownedJobsProbe = { error : { code : error ?. code ?? null , category : error ?. category ?? null , details : error ?. details ?? null } } ; } releaseDiagnostic = `release-stage ${ JSON . stringify ( { hookElapsedMs, stopObserved : callsAtFailure . some ( ( call ) => call . method === 'session/stop' && call . params ?. sessionId === 'zcode-a' ) , hookSocketMethods : ( await readFile ( socketMethods , 'utf8' ) ) . trim ( ) . split ( '\n' ) . filter ( Boolean ) , ownerStoreReplaced : ownershipAfter . ino !== ownershipBefore . ino || ownershipAfter . mtimeMs !== ownershipBefore . mtimeMs , healthyAfterFailure, childPidProbe, ownedJobsProbe, hookCode : ended . code , hookDiagnostic : ended . stderr . trim ( ) || null } ) } ` ; }
294295 assert . deepEqual ( owners . sessions , { 'zcode-b' : ownerIdForSession ( 'b' ) } , releaseDiagnostic ) ;
296+ assert . deepEqual ( ( await readFile ( socketMethods , 'utf8' ) ) . trim ( ) . split ( '\n' ) , [ 'broker/auth' , 'broker/health' , 'broker/releaseOwner' ] ) ;
295297 const calls = ( await readFile ( record , 'utf8' ) ) . trim ( ) . split ( '\n' ) . filter ( Boolean ) . map ( ( line ) => JSON . parse ( line ) ) ;
296298 assert . ok ( calls . some ( ( call ) => call . method === 'session/stop' && call . params ?. sessionId === 'zcode-a' ) ) ;
297299 assert . ok ( ! calls . some ( ( call ) => call . method === 'session/stop' && call . params ?. sessionId === 'zcode-b' ) ) ;
0 commit comments