@@ -3228,6 +3228,76 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
32283228 } ) ) ,
32293229 ) ;
32303230
3231+ /** Resolves a stored provider thread the session may act on: one whose
3232+ * ancestry reaches this session's root within the supported nesting depth.
3233+ * Returns the thread's metadata; anything else is a request error. */
3234+ const authorizeSubagentThread = Effect . fn ( "authorizeSubagentThread" ) ( function * (
3235+ context : CodexAdapterSessionContext ,
3236+ threadId : ThreadId ,
3237+ agentId : string ,
3238+ options : {
3239+ readonly requestError : ( detail : string ) => ProviderAdapterRequestError ;
3240+ readonly parentThreadDetail : string ;
3241+ } ,
3242+ ) {
3243+ const { requestError } = options ;
3244+ const rootThreadId = yield * context . runtime . readProviderThreadId . pipe (
3245+ Effect . mapError ( ( cause ) => mapCodexRuntimeError ( threadId , "thread/read" , cause ) ) ,
3246+ ) ;
3247+ if ( agentId === rootThreadId ) {
3248+ return yield * requestError ( options . parentThreadDetail ) ;
3249+ }
3250+
3251+ const readStoredThreadMetadata = ( providerThreadId : string ) =>
3252+ context . runtime
3253+ . readStoredThreadMetadata ( providerThreadId )
3254+ . pipe ( Effect . mapError ( ( cause ) => mapCodexRuntimeError ( threadId , "thread/read" , cause ) ) ) ;
3255+ const candidate = yield * readStoredThreadMetadata ( agentId ) ;
3256+ const visited = new Set < string > ( [ candidate . id ] ) ;
3257+ let current = candidate ;
3258+
3259+ for ( let depth = 0 ; depth < CODEX_SUBAGENT_MAX_ANCESTRY_DEPTH ; depth += 1 ) {
3260+ const parentThreadId = readCodexSubagentParentThreadId ( current ) ;
3261+ if ( parentThreadId === rootThreadId ) {
3262+ return candidate ;
3263+ }
3264+ if ( ! parentThreadId || visited . has ( parentThreadId ) ) {
3265+ return yield * requestError (
3266+ `Codex thread '${ agentId } ' is not a subagent of this conversation.` ,
3267+ ) ;
3268+ }
3269+ visited . add ( parentThreadId ) ;
3270+ current = yield * readStoredThreadMetadata ( parentThreadId ) ;
3271+ }
3272+
3273+ return yield * requestError (
3274+ `Codex thread '${ agentId } ' exceeded the supported subagent nesting depth.` ,
3275+ ) ;
3276+ } ) ;
3277+
3278+ const sendSubagentInput : NonNullable < CodexAdapterShape [ "sendSubagentInput" ] > = Effect . fn (
3279+ "sendSubagentInput" ,
3280+ ) ( function * ( threadId , input ) {
3281+ const requestError = ( detail : string ) =>
3282+ new ProviderAdapterRequestError ( {
3283+ provider : PROVIDER ,
3284+ method : "sendSubagentInput" ,
3285+ detail,
3286+ } ) ;
3287+ const context = yield * requireSession ( threadId ) ;
3288+ const candidate = yield * authorizeSubagentThread ( context , threadId , input . agentId , {
3289+ requestError,
3290+ parentThreadDetail : "Send to the parent thread through the composer instead." ,
3291+ } ) ;
3292+ if ( candidate . canAcceptDirectInput === false ) {
3293+ return yield * requestError ( "This agent does not accept direct input right now." ) ;
3294+ }
3295+ const response = yield * context . runtime
3296+ . startStoredThreadTurn ( candidate . id , input . text )
3297+ . pipe ( Effect . mapError ( ( cause ) => mapCodexRuntimeError ( threadId , "turn/start" , cause ) ) ) ;
3298+ return { turnId : response . turn . id } ;
3299+ } ) ;
3300+
32313301 const readSubagentTranscript : NonNullable < CodexAdapterShape [ "readSubagentTranscript" ] > =
32323302 Effect . fn ( "readSubagentTranscript" ) ( function * ( threadId , input ) {
32333303 const requestError = ( detail : string ) =>
@@ -3237,42 +3307,10 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
32373307 detail,
32383308 } ) ;
32393309 const context = yield * requireSession ( threadId ) ;
3240- const rootThreadId = yield * context . runtime . readProviderThreadId . pipe (
3241- Effect . mapError ( ( cause ) => mapCodexRuntimeError ( threadId , "thread/read" , cause ) ) ,
3242- ) ;
3243- if ( input . agentId === rootThreadId ) {
3244- return yield * requestError ( "The requested transcript belongs to the parent thread." ) ;
3245- }
3246-
3247- const readStoredThreadMetadata = ( providerThreadId : string ) =>
3248- context . runtime
3249- . readStoredThreadMetadata ( providerThreadId )
3250- . pipe ( Effect . mapError ( ( cause ) => mapCodexRuntimeError ( threadId , "thread/read" , cause ) ) ) ;
3251- const candidate = yield * readStoredThreadMetadata ( input . agentId ) ;
3252- const visited = new Set < string > ( [ candidate . id ] ) ;
3253- let current = candidate ;
3254- let authorized = false ;
3255-
3256- for ( let depth = 0 ; depth < CODEX_SUBAGENT_MAX_ANCESTRY_DEPTH ; depth += 1 ) {
3257- const parentThreadId = readCodexSubagentParentThreadId ( current ) ;
3258- if ( parentThreadId === rootThreadId ) {
3259- authorized = true ;
3260- break ;
3261- }
3262- if ( ! parentThreadId || visited . has ( parentThreadId ) ) {
3263- return yield * requestError (
3264- `Codex thread '${ input . agentId } ' is not a subagent of this conversation.` ,
3265- ) ;
3266- }
3267- visited . add ( parentThreadId ) ;
3268- current = yield * readStoredThreadMetadata ( parentThreadId ) ;
3269- }
3270-
3271- if ( ! authorized ) {
3272- return yield * requestError (
3273- `Codex thread '${ input . agentId } ' exceeded the supported subagent nesting depth.` ,
3274- ) ;
3275- }
3310+ const candidate = yield * authorizeSubagentThread ( context , threadId , input . agentId , {
3311+ requestError,
3312+ parentThreadDetail : "The requested transcript belongs to the parent thread." ,
3313+ } ) ;
32763314
32773315 // Legacy threads expose their stored turns through `thread/read`. The
32783316 // cursor API only exists for Codex's explicit paginated history mode;
@@ -3638,6 +3676,7 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* (
36383676 clearThreadGoal,
36393677 readThread,
36403678 readSubagentTranscript,
3679+ sendSubagentInput,
36413680 rollbackThread,
36423681 deleteThread,
36433682 respondToRequest,
0 commit comments