@@ -328,7 +328,13 @@ describe("tool handlers — claim helpers (pure, no network)", () => {
328328 const realFetch = globalThis . fetch ;
329329 ( globalThis as any ) . fetch = ( async ( ) =>
330330 new Response (
331- JSON . stringify ( { ok : true , resource_type : "x" , token : "t" , tier : "free" , status : "active" } ) ,
331+ JSON . stringify ( {
332+ ok : true ,
333+ team_id : "t-1" ,
334+ user_id : "u-1" ,
335+ session_token : "sess.jwt" ,
336+ message : "Magic link sent" ,
337+ } ) ,
332338 { status : 200 , headers : { "content-type" : "application/json" } }
333339 ) ) as typeof globalThis . fetch ;
334340 try {
@@ -337,19 +343,28 @@ describe("tool handlers — claim helpers (pure, no network)", () => {
337343 email : "u@example.com" ,
338344 } ) ;
339345 const text = flat ( res ) ;
340- assert . match ( text , / J W T c l a i m e d \. / ) ;
346+ assert . match ( text , / C l a i m a c c e p t e d f o r u @ e x a m p l e \. c o m \. / ) ;
341347 } finally {
342348 ( globalThis as any ) . fetch = realFetch ;
343349 }
344350 } ) ;
345351
346- it ( "claim_token → raw JWT + email → JWT claimed; mock returns magic-link shape" , async ( ) => {
352+ it ( "claim_token → raw JWT + email → renders team_id/user_id/session_token from live ClaimResponse shape" , async ( ) => {
347353 const res = await handlerFor ( "claim_token" ) ( {
348354 upgrade_jwt : "ey.valid.jwt" ,
349355 email : "u@example.com" ,
350356 } ) ;
351357 const text = flat ( res ) ;
352- assert . match ( text , / J W T c l a i m e d \. / ) ;
358+ assert . match ( text , / C l a i m a c c e p t e d f o r u @ e x a m p l e \. c o m \. / ) ;
359+ assert . match ( text , / T e a m I D : / ) ;
360+ assert . match ( text , / U s e r I D : / ) ;
361+ // Mock-api returns a session_token, so the session-token block must
362+ // render and the agent must be told how to use it as INSTANODE_TOKEN.
363+ assert . match ( text , / S e s s i o n t o k e n \( 2 4 h , r e a d y t o u s e \) : / ) ;
364+ assert . match ( text , / I N S T A N O D E _ T O K E N / ) ;
365+ // Guard against the placeholder regression from before this fix —
366+ // the previous renderer printed "(see list_resources)" on every line.
367+ assert . doesNotMatch ( text , / \( s e e l i s t _ r e s o u r c e s \) / ) ;
353368 } ) ;
354369
355370 it ( "claim_token → URL-form upgrade_jwt extracted via URL parse branch" , async ( ) => {
@@ -358,7 +373,7 @@ describe("tool handlers — claim helpers (pure, no network)", () => {
358373 email : "u@example.com" ,
359374 } ) ;
360375 const text = flat ( res ) ;
361- assert . match ( text , / J W T c l a i m e d \. / ) ;
376+ assert . match ( text , / C l a i m a c c e p t e d f o r u @ e x a m p l e \. c o m \. / ) ;
362377 } ) ;
363378
364379 it ( "claim_token → already-claimed conflict surfaces the formatError envelope" , async ( ) => {
@@ -1092,7 +1107,11 @@ describe("tool handlers — optional-field absent branches", () => {
10921107 assert . doesNotMatch ( text , / M e s s a g e : / ) ;
10931108 } ) ;
10941109
1095- it ( "claim_token → result missing optional fields: fallbacks to '(see list_resources)' chain" , async ( ) => {
1110+ it ( "claim_token → bare {ok:true} body: renders the magic-link branch (no session_token, no Team ID lines)" , async ( ) => {
1111+ // The api's ClaimResponse shape post-2026-05-20 always carries team_id +
1112+ // user_id + message — but a defensive minimal {ok:true} body still has to
1113+ // render without throwing. We must NOT regress to the old placeholder
1114+ // "(see list_resources)" lines this fix removed.
10961115 ( globalThis as any ) . fetch = ( async ( ) =>
10971116 new Response ( JSON . stringify ( { ok : true } ) , {
10981117 status : 200 ,
@@ -1103,20 +1122,28 @@ describe("tool handlers — optional-field absent branches", () => {
11031122 email : "u@example.com" ,
11041123 } ) ;
11051124 const text = flat ( res ) ;
1106- assert . match ( text , / J W T c l a i m e d \. / ) ;
1107- assert . match ( text , / \( s e e l i s t _ r e s o u r c e s \) / ) ;
1108- } ) ;
1109-
1110- it ( "claim_token → result with `name` field renders 'Name: ...' line" , async ( ) => {
1125+ assert . match ( text , / C l a i m a c c e p t e d f o r u @ e x a m p l e \. c o m \. / ) ;
1126+ // No team_id / user_id / session_token / message in this body → none rendered.
1127+ assert . doesNotMatch ( text , / T e a m I D : / ) ;
1128+ assert . doesNotMatch ( text , / U s e r I D : / ) ;
1129+ assert . doesNotMatch ( text , / S e s s i o n t o k e n / ) ;
1130+ // Falls into the magic-link branch (no session_token returned).
1131+ assert . match ( text , / M a g i c l i n k s e n t t o u @ e x a m p l e \. c o m / ) ;
1132+ // Critical regression guard: never re-introduce the retired placeholder
1133+ // text the old renderer printed for every missing field.
1134+ assert . doesNotMatch ( text , / \( s e e l i s t _ r e s o u r c e s \) / ) ;
1135+ } ) ;
1136+
1137+ it ( "claim_token → ClaimResponse with team_id + user_id + message but no session_token: renders all four lines + magic-link branch" , async ( ) => {
1138+ // Live magic-link envelope shape — what mock-api returns by default and
1139+ // what api/internal/handlers/onboarding.go currently emits.
11111140 ( globalThis as any ) . fetch = ( async ( ) =>
11121141 new Response (
11131142 JSON . stringify ( {
11141143 ok : true ,
1115- resource_type : "postgres" ,
1116- token : "t" ,
1117- tier : "free" ,
1118- status : "active" ,
1119- name : "my-claimed-db" ,
1144+ team_id : "team-uuid" ,
1145+ user_id : "user-uuid" ,
1146+ message : "Magic link sent to email" ,
11201147 } ) ,
11211148 { status : 200 , headers : { "content-type" : "application/json" } }
11221149 ) ) as typeof globalThis . fetch ;
@@ -1125,7 +1152,12 @@ describe("tool handlers — optional-field absent branches", () => {
11251152 email : "u@example.com" ,
11261153 } ) ;
11271154 const text = flat ( res ) ;
1128- assert . match ( text , / N a m e : m y - c l a i m e d - d b / ) ;
1155+ assert . match ( text , / T e a m I D : t e a m - u u i d / ) ;
1156+ assert . match ( text , / U s e r I D : u s e r - u u i d / ) ;
1157+ assert . match ( text , / M e s s a g e : M a g i c l i n k s e n t t o e m a i l / ) ;
1158+ // No session_token → magic-link guidance, NOT the immediate-use block.
1159+ assert . doesNotMatch ( text , / S e s s i o n t o k e n / ) ;
1160+ assert . match ( text , / M a g i c l i n k s e n t t o u @ e x a m p l e \. c o m / ) ;
11291161 } ) ;
11301162
11311163 it ( "create_deploy → response url is empty string: shows 'URL: (pending)'" , async ( ) => {
0 commit comments