@@ -34,8 +34,8 @@ function assert(condition, message, details) {
3434 }
3535}
3636
37- async function callTool ( name , args ) {
38- return client . callTool (
37+ async function callToolOn ( targetClient , name , args ) {
38+ return targetClient . callTool (
3939 {
4040 name,
4141 arguments : args ,
@@ -44,6 +44,10 @@ async function callTool(name, args) {
4444 ) ;
4545}
4646
47+ async function callTool ( name , args ) {
48+ return callToolOn ( client , name , args ) ;
49+ }
50+
4751async function readJsonResource ( uri ) {
4852 const resource = await client . readResource ( { uri } ) ;
4953 return JSON . parse ( resource . contents [ 0 ] . text ) ;
@@ -73,6 +77,10 @@ async function recordedCalls() {
7377}
7478
7579async function listToolsWithEnv ( env ) {
80+ return withClientEnv ( env , async ( debugClient ) => debugClient . listTools ( ) ) ;
81+ }
82+
83+ async function withClientEnv ( env , run ) {
7684 const debugClient = new Client ( { name : "codex-subagents-smoke-debug" , version : "0.1.0" } ) ;
7785 const debugTransport = new StdioClientTransport ( {
7886 command : path . join ( root , "dist/index.js" ) ,
@@ -83,7 +91,7 @@ async function listToolsWithEnv(env) {
8391 debugTransport . stderr ?. resume ( ) ;
8492 try {
8593 await debugClient . connect ( debugTransport ) ;
86- return await debugClient . listTools ( ) ;
94+ return await run ( debugClient ) ;
8795 } finally {
8896 await debugTransport . close ( ) . catch ( ( ) => { } ) ;
8997 }
@@ -192,6 +200,50 @@ try {
192200 missingCancel ,
193201 ) ;
194202
203+ await withClientEnv (
204+ {
205+ PATH : process . env . PATH ?? "" ,
206+ CODEX_SUBAGENTS_CODEX_BIN : fakeCodex ,
207+ CLAUDE_PROJECT_DIR : projectDir ,
208+ CODEX_SUBAGENTS_SESSION_STATE_FILE : path . join ( projectDir , "capped-wait-sessions.json" ) ,
209+ CODEX_SUBAGENTS_MAX_BLOCKING_WAIT_MS : "50" ,
210+ FAKE_CODEX_RECORD_DIR : recordDir ,
211+ } ,
212+ async ( cappedClient ) => {
213+ const slow = await callToolOn ( cappedClient , "codex_task" , {
214+ description : "Capped wait smoke" ,
215+ prompt : "capped wait smoke DELAY_MS=500" ,
216+ project_dir : projectDir ,
217+ background : true ,
218+ } ) ;
219+ assert ( slow . structuredContent ?. session_id , "capped wait smoke should start a background session" , slow ) ;
220+ const cappedWait = await callToolOn ( cappedClient , "codex_wait_any" , {
221+ session_ids : [ slow . structuredContent . session_id ] ,
222+ wait_timeout_ms : 5_000 ,
223+ } ) ;
224+ assert (
225+ cappedWait . structuredContent ?. completed === false &&
226+ cappedWait . structuredContent ?. wait_timeout_capped === true &&
227+ cappedWait . structuredContent ?. effective_wait_timeout_ms === 50 &&
228+ cappedWait . structuredContent ?. requested_wait_timeout_ms === 5_000 ,
229+ "codex_wait_any should cap long blocking waits and return a running result" ,
230+ cappedWait . structuredContent ,
231+ ) ;
232+ assert (
233+ typeof cappedWait . structuredContent ?. elapsed_ms === "number" &&
234+ cappedWait . structuredContent . elapsed_ms < 1_000 ,
235+ "capped wait should return before Claude Desktop's inactivity watchdog could fire" ,
236+ cappedWait . structuredContent ,
237+ ) ;
238+ const cancelled = await callToolOn ( cappedClient , "codex_followup" , {
239+ session_id : slow . structuredContent . session_id ,
240+ mode : "cancel" ,
241+ reason : "capped wait smoke cleanup" ,
242+ } ) ;
243+ assert ( cancelled . structuredContent ?. status === "cancelled" , "capped wait smoke cleanup should cancel the session" , cancelled ) ;
244+ } ,
245+ ) ;
246+
195247 const invalidReasoning = await callTool ( "codex_task" , {
196248 description : "Invalid reasoning smoke" ,
197249 prompt : "should not start" ,
@@ -334,6 +386,29 @@ try {
334386 "codex_followup without an explicit description should not prepend boilerplate" ,
335387 followupCall ,
336388 ) ;
389+ const followupTimeoutSession = await callTool ( "codex_task" , {
390+ description : "Follow-up timeout session" ,
391+ prompt : "follow-up timeout initial" ,
392+ project_dir : projectDir ,
393+ keep_session : true ,
394+ } ) ;
395+ const followupTimedOut = await callTool ( "codex_followup" , {
396+ session_id : followupTimeoutSession . structuredContent . session_id ,
397+ prompt : "follow-up timeout DELAY_MS=500" ,
398+ wait_timeout_ms : 20 ,
399+ } ) ;
400+ assert (
401+ followupTimedOut . structuredContent ?. completed === false &&
402+ followupTimedOut . structuredContent ?. timeoutReason === "wait_timeout" &&
403+ followupTimedOut . structuredContent ?. effective_wait_timeout_ms === 20 ,
404+ "foreground codex_followup should honor wait_timeout_ms instead of waiting indefinitely" ,
405+ followupTimedOut . structuredContent ,
406+ ) ;
407+ await callTool ( "codex_followup" , {
408+ session_id : followupTimeoutSession . structuredContent . session_id ,
409+ mode : "cancel" ,
410+ reason : "follow-up timeout smoke cleanup" ,
411+ } ) ;
337412 const personaCall = calls . find ( ( call ) => call . method === "turn/start" && call . prompt ?. includes ( "persona smoke" ) ) ;
338413 assert ( personaCall , "expected recorded persona turn/start call" , calls ) ;
339414 assert (
0 commit comments