@@ -2306,6 +2306,111 @@ describe("AgentServer HTTP Mode", () => {
23062306 expect ( body . result ?. stopReason ) . toBe ( "end_turn" ) ;
23072307 expect ( prompt ) . toHaveBeenCalledTimes ( 2 ) ;
23082308 } , 20000 ) ;
2309+
2310+ // Shared plumbing for the relay-echo tests: install a controllable
2311+ // prompt, stub the log writer so relayAgentResponse has an answer to
2312+ // relay, and spy on the relay_message client call.
2313+ const setupRelayEchoServer = async (
2314+ prompt : ( ) => Promise < { stopReason : string } > ,
2315+ ) => {
2316+ const s = createServer ( ) ;
2317+ await s . start ( ) ;
2318+ const serverInternals = s as unknown as {
2319+ session : {
2320+ clientConnection : { prompt : typeof prompt } ;
2321+ logWriter : {
2322+ getFullAgentResponse : ( runId : string ) => string | undefined ;
2323+ getAgentResponseParts : ( runId : string ) => string [ ] ;
2324+ } ;
2325+ } ;
2326+ posthogAPI : PostHogAPIClient ;
2327+ } ;
2328+ serverInternals . session . clientConnection . prompt = prompt ;
2329+ vi . spyOn (
2330+ serverInternals . session . logWriter ,
2331+ "getFullAgentResponse" ,
2332+ ) . mockReturnValue ( "final answer" ) ;
2333+ vi . spyOn (
2334+ serverInternals . session . logWriter ,
2335+ "getAgentResponseParts" ,
2336+ ) . mockReturnValue ( [ "final answer" ] ) ;
2337+ const relaySpy = vi
2338+ . spyOn ( serverInternals . posthogAPI , "relayMessage" )
2339+ . mockResolvedValue ( undefined ) ;
2340+
2341+ const token = createToken ( ) ;
2342+ const send = ( messageId ?: string ) =>
2343+ fetch ( `http://localhost:${ port } /command` , {
2344+ method : "POST" ,
2345+ headers : {
2346+ Authorization : `Bearer ${ token } ` ,
2347+ "Content-Type" : "application/json" ,
2348+ } ,
2349+ body : JSON . stringify ( {
2350+ jsonrpc : "2.0" ,
2351+ id : messageId ?? "no-id" ,
2352+ method : "user_message" ,
2353+ params : {
2354+ content : "do the thing" ,
2355+ ...( messageId ? { messageId } : { } ) ,
2356+ } ,
2357+ } ) ,
2358+ } ) ;
2359+
2360+ return { relaySpy, send } ;
2361+ } ;
2362+
2363+ it ( "echoes each turn's own initiating messageId on relay_message" , async ( ) => {
2364+ const pendingTurns : Array < ( result : { stopReason : string } ) => void > = [ ] ;
2365+ const prompt = vi . fn (
2366+ ( ) =>
2367+ new Promise < { stopReason : string } > ( ( resolve ) => {
2368+ pendingTurns . push ( resolve ) ;
2369+ } ) ,
2370+ ) ;
2371+ const { relaySpy, send } = await setupRelayEchoServer ( prompt ) ;
2372+
2373+ // The second message lands while the first turn is still in flight;
2374+ // each relay carries its own sender's id, not the first turn's.
2375+ const first = send ( "m-first" ) ;
2376+ await vi . waitFor ( ( ) => expect ( prompt ) . toHaveBeenCalledTimes ( 1 ) ) ;
2377+ const second = send ( "m-second" ) ;
2378+ await vi . waitFor ( ( ) => expect ( prompt ) . toHaveBeenCalledTimes ( 2 ) ) ;
2379+
2380+ pendingTurns [ 0 ] ( { stopReason : "end_turn" } ) ;
2381+ await first ;
2382+ await vi . waitFor ( ( ) => expect ( relaySpy ) . toHaveBeenCalledTimes ( 1 ) ) ;
2383+ expect ( relaySpy . mock . calls [ 0 ] [ 4 ] ) . toBe ( "m-first" ) ;
2384+
2385+ pendingTurns [ 1 ] ( { stopReason : "end_turn" } ) ;
2386+ await second ;
2387+ await vi . waitFor ( ( ) => expect ( relaySpy ) . toHaveBeenCalledTimes ( 2 ) ) ;
2388+ expect ( relaySpy . mock . calls [ 1 ] [ 4 ] ) . toBe ( "m-second" ) ;
2389+
2390+ // A message without an id relays without correlation (backward
2391+ // compatible with backends that don't know message_id).
2392+ relaySpy . mockClear ( ) ;
2393+ const anonymous = send ( undefined ) ;
2394+ await vi . waitFor ( ( ) => expect ( prompt ) . toHaveBeenCalledTimes ( 3 ) ) ;
2395+ pendingTurns [ 2 ] ( { stopReason : "end_turn" } ) ;
2396+ await anonymous ;
2397+ await vi . waitFor ( ( ) => expect ( relaySpy ) . toHaveBeenCalledTimes ( 1 ) ) ;
2398+ expect ( relaySpy . mock . calls [ 0 ] [ 4 ] ) . toBeUndefined ( ) ;
2399+ } , 20000 ) ;
2400+
2401+ it ( "does not leak a failed turn's messageId into the next turn" , async ( ) => {
2402+ const prompt = vi
2403+ . fn ( async ( ) => ( { stopReason : "end_turn" } ) )
2404+ . mockRejectedValueOnce ( new Error ( "sdk connection lost" ) ) ;
2405+ const { relaySpy, send } = await setupRelayEchoServer ( prompt ) ;
2406+
2407+ await send ( "m-fail" ) ;
2408+ expect ( relaySpy ) . not . toHaveBeenCalled ( ) ;
2409+
2410+ await send ( "m-next" ) ;
2411+ await vi . waitFor ( ( ) => expect ( relaySpy ) . toHaveBeenCalledTimes ( 1 ) ) ;
2412+ expect ( relaySpy . mock . calls [ 0 ] [ 4 ] ) . toBe ( "m-next" ) ;
2413+ } , 20000 ) ;
23092414 } ) ;
23102415
23112416 describe ( "404 handling" , ( ) => {
0 commit comments