@@ -15,6 +15,7 @@ import type {
1515 RequestPermissionResponse ,
1616 ResumeSessionRequest ,
1717 ResumeSessionResponse ,
18+ SessionNotification ,
1819 SetSessionConfigOptionRequest ,
1920 SetSessionConfigOptionResponse ,
2021 StopReason ,
@@ -236,6 +237,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
236237 /** Deployment environment; on "cloud" a non-danger sandbox would panic, so we skip the override. */
237238 private environment ?: "local" | "cloud" ;
238239 private readonly commandOutputs = new Map < string , string > ( ) ;
240+ private readonly subagentParents = new Map < string , string > ( ) ;
239241 /** Extra writable roots for this session, folded into workspaceWrite sandbox turns. */
240242 private additionalDirectories ?: string [ ] ;
241243 /** The session workspace stays writable when extra roots are applied per turn. */
@@ -460,6 +462,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
460462 ) : Promise < { threadId : string ; thread : AppServerThread | undefined } > {
461463 this . cancelNextGoalTurn = false ;
462464 this . nativeGoalTurnId = undefined ;
465+ this . subagentParents . clear ( ) ;
463466 this . jsonSchema = params . meta ?. jsonSchema ?? undefined ;
464467 this . taskRunId = params . meta ?. taskRunId ;
465468 this . environment = params . meta ?. environment ;
@@ -1159,6 +1162,7 @@ export class CodexAppServerAgent extends BaseAcpAgent {
11591162
11601163 async closeSession ( ) : Promise < void > {
11611164 this . commandOutputs . clear ( ) ;
1165+ this . subagentParents . clear ( ) ;
11621166 this . nativeGoalTurnId = undefined ;
11631167 this . session . abortController . abort ( ) ;
11641168 this . session . cancelled = true ;
@@ -1178,23 +1182,25 @@ export class CodexAppServerAgent extends BaseAcpAgent {
11781182 const notificationThreadId = readNotificationThreadId ( params ) ;
11791183 const isMainThread =
11801184 ! notificationThreadId || notificationThreadId === this . threadId ;
1185+ this . captureSubagentRelationship ( method , params , notificationThreadId ) ;
11811186 const mappedParams = isMainThread
11821187 ? this . withBufferedCommandOutput ( method , params )
11831188 : params ;
11841189
11851190 if ( this . sessionId && ! this . session . cancelled ) {
1186- if ( isMainThread ) {
1187- const notification = mapAppServerNotification (
1188- this . sessionId ,
1189- method ,
1190- mappedParams ,
1191- ) ;
1192- if ( notification ) {
1193- void this . client
1194- . sessionUpdate ( notification )
1195- . catch ( ( err ) => this . logger . warn ( "sessionUpdate failed" , err ) ) ;
1196- this . appendNotification ( this . sessionId , notification ) ;
1197- }
1191+ const notification = mapAppServerNotification (
1192+ this . sessionId ,
1193+ method ,
1194+ mappedParams ,
1195+ ) ;
1196+ const visibleNotification = isMainThread
1197+ ? notification
1198+ : this . mapSubagentNotification ( notification , notificationThreadId ) ;
1199+ if ( visibleNotification ) {
1200+ void this . client
1201+ . sessionUpdate ( visibleNotification )
1202+ . catch ( ( err ) => this . logger . warn ( "sessionUpdate failed" , err ) ) ;
1203+ this . appendNotification ( this . sessionId , visibleNotification ) ;
11981204 }
11991205 }
12001206
@@ -1307,6 +1313,87 @@ export class CodexAppServerAgent extends BaseAcpAgent {
13071313 }
13081314 }
13091315
1316+ private captureSubagentRelationship (
1317+ method : string ,
1318+ params : unknown ,
1319+ senderThreadId : string | undefined ,
1320+ ) : void {
1321+ if (
1322+ method !== APP_SERVER_NOTIFICATIONS . ITEM_STARTED &&
1323+ method !== APP_SERVER_NOTIFICATIONS . ITEM_COMPLETED
1324+ ) {
1325+ return ;
1326+ }
1327+ const item = ( params as { item ?: AppServerItem } ) ?. item ;
1328+ if (
1329+ item ?. type !== "collabAgentToolCall" ||
1330+ item . tool !== "spawnAgent" ||
1331+ ! item . id ||
1332+ ! item . receiverThreadIds ?. length
1333+ ) {
1334+ return ;
1335+ }
1336+ const parentToolCallId =
1337+ senderThreadId && senderThreadId !== this . threadId
1338+ ? subagentToolCallId ( senderThreadId , item . id )
1339+ : item . id ;
1340+ for ( const receiverThreadId of item . receiverThreadIds ) {
1341+ this . subagentParents . set ( receiverThreadId , parentToolCallId ) ;
1342+ }
1343+ }
1344+
1345+ private mapSubagentNotification (
1346+ notification : SessionNotification | null ,
1347+ threadId : string | undefined ,
1348+ ) : SessionNotification | null {
1349+ if ( ! notification || ! threadId ) return null ;
1350+ const parentToolCallId = this . subagentParents . get ( threadId ) ;
1351+ if ( ! parentToolCallId ) return null ;
1352+ const update = notification . update as SessionNotification [ "update" ] & {
1353+ _meta ?: Record < string , unknown > ;
1354+ toolCallId ?: string ;
1355+ } ;
1356+ if (
1357+ update . sessionUpdate !== "agent_message_chunk" &&
1358+ update . sessionUpdate !== "agent_thought_chunk" &&
1359+ update . sessionUpdate !== "tool_call" &&
1360+ update . sessionUpdate !== "tool_call_update"
1361+ ) {
1362+ return null ;
1363+ }
1364+ const toolCallId = update . toolCallId
1365+ ? subagentToolCallId ( threadId , update . toolCallId )
1366+ : undefined ;
1367+ if ( update . sessionUpdate === "tool_call_update" ) {
1368+ return {
1369+ ...notification ,
1370+ update : { ...update , ...( toolCallId ? { toolCallId } : { } ) } ,
1371+ } as SessionNotification ;
1372+ }
1373+ const existingPosthog = ( update . _meta ?. posthog ?? { } ) as Record <
1374+ string ,
1375+ unknown
1376+ > ;
1377+ return {
1378+ ...notification ,
1379+ update : {
1380+ ...update ,
1381+ ...( toolCallId ? { toolCallId } : { } ) ,
1382+ _meta : {
1383+ ...update . _meta ,
1384+ posthog : {
1385+ toolName :
1386+ typeof existingPosthog . toolName === "string"
1387+ ? existingPosthog . toolName
1388+ : "subagent_activity" ,
1389+ ...existingPosthog ,
1390+ parentToolCallId,
1391+ } ,
1392+ } ,
1393+ } ,
1394+ } as SessionNotification ;
1395+ }
1396+
13101397 private withBufferedCommandOutput ( method : string , params : unknown ) : unknown {
13111398 if ( ! params || typeof params !== "object" ) {
13121399 return params ;
@@ -1718,6 +1805,10 @@ function readNotificationThreadId(params: unknown): string | undefined {
17181805 return typeof threadId === "string" ? threadId : undefined ;
17191806}
17201807
1808+ function subagentToolCallId ( threadId : string , toolCallId : string ) : string {
1809+ return `subagent:${ threadId } :${ toolCallId } ` ;
1810+ }
1811+
17211812/** The codex thread config override map: folds in MCP servers + makes extra workspace roots writable. Undefined when empty. */
17221813function buildThreadConfig (
17231814 mcpServers : ReturnType < typeof toCodexMcpServers > ,
0 commit comments