@@ -33,8 +33,12 @@ import { getAgentRegistry } from '../plugins/agents/registry.js';
3333import { getTrackerRegistry } from '../plugins/trackers/registry.js' ;
3434import { SubagentTraceParser } from '../plugins/agents/tracing/parser.js' ;
3535import type { SubagentEvent } from '../plugins/agents/tracing/types.js' ;
36- import { ClaudeAgentPlugin } from '../plugins/agents/builtin/claude.js' ;
36+ import type { ClaudeJsonlMessage } from '../plugins/agents/builtin/claude.js' ;
3737import { createDroidStreamingJsonlParser , isDroidJsonlMessage , toClaudeJsonlMessages } from '../plugins/agents/droid/outputParser.js' ;
38+ import {
39+ isOpenCodeTaskTool ,
40+ openCodeTaskToClaudeMessages ,
41+ } from '../plugins/agents/opencode/outputParser.js' ;
3842import { updateSessionIteration , updateSessionStatus , updateSessionMaxIterations } from '../session/index.js' ;
3943import { saveIterationLog , buildSubagentTrace , createProgressEntry , appendProgress , getRecentProgressSummary , getCodebasePatternsForPrompt } from '../logs/index.js' ;
4044import type { AgentSwitchEntry } from '../logs/index.js' ;
@@ -807,15 +811,13 @@ export class ExecutionEngine {
807811 flags . push ( '--model' , this . config . model ) ;
808812 }
809813
810- // Check if agent supports subagent tracing
814+ // Check if agent declares subagent tracing support (used for agent-specific flags)
811815 const supportsTracing = this . agent ! . meta . supportsSubagentTracing ;
812816
813- // Create streaming JSONL parser if tracing is enabled
814- const jsonlParser = supportsTracing
815- ? this . agent ?. meta . id === 'droid'
816- ? createDroidStreamingJsonlParser ( )
817- : ClaudeAgentPlugin . createStreamingJsonlParser ( )
818- : null ;
817+ // For Droid agent, we need a JSONL parser since it uses a different output format.
818+ // For Claude and OpenCode, we use the onJsonlMessage callback which gets pre-parsed messages.
819+ const isDroidAgent = this . agent ?. meta . id === 'droid' ;
820+ const droidJsonlParser = isDroidAgent ? createDroidStreamingJsonlParser ( ) : null ;
819821
820822 try {
821823 // Execute agent with subagent tracing if supported
@@ -824,6 +826,42 @@ export class ExecutionEngine {
824826 flags,
825827 sandbox : this . config . sandbox ,
826828 subagentTracing : supportsTracing ,
829+ // Callback for pre-parsed JSONL messages (used by Claude and OpenCode plugins)
830+ // This receives raw JSON objects directly from the agent's parsed JSONL output.
831+ onJsonlMessage : ( message : Record < string , unknown > ) => {
832+ // Check if this is OpenCode format (has 'part' with 'tool' property)
833+ const part = message . part as Record < string , unknown > | undefined ;
834+ if ( message . type === 'tool_use' && part ?. tool ) {
835+ // OpenCode format - convert using OpenCode parser
836+ const openCodeMessage = {
837+ source : 'opencode' as const ,
838+ type : message . type as string ,
839+ timestamp : message . timestamp as number | undefined ,
840+ sessionID : message . sessionID as string | undefined ,
841+ part : part as import ( '../plugins/agents/opencode/outputParser.js' ) . OpenCodePart ,
842+ raw : message ,
843+ } ;
844+ // Check if it's a Task tool and convert to Claude format
845+ if ( isOpenCodeTaskTool ( openCodeMessage ) ) {
846+ for ( const claudeMessage of openCodeTaskToClaudeMessages ( openCodeMessage ) ) {
847+ this . subagentParser . processMessage ( claudeMessage ) ;
848+ }
849+ }
850+ return ;
851+ }
852+
853+ // Claude format - convert raw JSON to ClaudeJsonlMessage format for SubagentParser
854+ const claudeMessage : ClaudeJsonlMessage = {
855+ type : message . type as string | undefined ,
856+ message : message . message as string | undefined ,
857+ tool : message . tool as { name ?: string ; input ?: Record < string , unknown > } | undefined ,
858+ result : message . result ,
859+ cost : message . cost as { inputTokens ?: number ; outputTokens ?: number ; totalUSD ?: number } | undefined ,
860+ sessionId : message . sessionId as string | undefined ,
861+ raw : message ,
862+ } ;
863+ this . subagentParser . processMessage ( claudeMessage ) ;
864+ } ,
827865 onStdout : ( data ) => {
828866 this . state . currentOutput += data ;
829867 this . emit ( {
@@ -834,9 +872,10 @@ export class ExecutionEngine {
834872 iteration,
835873 } ) ;
836874
837- // Parse JSONL output for subagent events if tracing is enabled
838- if ( jsonlParser ) {
839- const results = jsonlParser . push ( data ) ;
875+ // For Droid agent, parse JSONL output for subagent events
876+ // (Claude uses onJsonlMessage callback instead)
877+ if ( droidJsonlParser && isDroidAgent ) {
878+ const results = droidJsonlParser . push ( data ) ;
840879 for ( const result of results ) {
841880 if ( result . success ) {
842881 if ( isDroidJsonlMessage ( result . message ) ) {
@@ -849,6 +888,7 @@ export class ExecutionEngine {
849888 }
850889 }
851890 }
891+
852892 } ,
853893 onStderr : ( data ) => {
854894 this . state . currentStderr += data ;
@@ -868,9 +908,9 @@ export class ExecutionEngine {
868908 const agentResult = await handle . promise ;
869909 this . currentExecution = null ;
870910
871- // Flush any remaining buffered JSONL data
872- if ( jsonlParser ) {
873- const remaining = jsonlParser . flush ( ) ;
911+ // Flush any remaining buffered JSONL data for Droid agent
912+ if ( droidJsonlParser && isDroidAgent ) {
913+ const remaining = droidJsonlParser . flush ( ) ;
874914 for ( const result of remaining ) {
875915 if ( result . success ) {
876916 if ( isDroidJsonlMessage ( result . message ) ) {
@@ -1371,6 +1411,58 @@ export class ExecutionEngine {
13711411 return depth ;
13721412 }
13731413
1414+ /**
1415+ * Get output/result for a specific subagent by ID.
1416+ * For completed subagents, returns their result content.
1417+ * For running subagents, returns undefined (use currentOutput for live streaming).
1418+ *
1419+ * @param id - Subagent ID to get output for
1420+ * @returns Subagent result content, or undefined if not found or still running
1421+ */
1422+ getSubagentOutput ( id : string ) : string | undefined {
1423+ const state = this . subagentParser . getSubagent ( id ) ;
1424+ if ( ! state ) return undefined ;
1425+ // Return result only for completed/errored subagents
1426+ if ( state . status === 'completed' || state . status === 'error' ) {
1427+ return state . result ;
1428+ }
1429+ return undefined ;
1430+ }
1431+
1432+ /**
1433+ * Get detailed information about a subagent for display.
1434+ * Returns the prompt, result, and timing information.
1435+ *
1436+ * @param id - Subagent ID to get details for
1437+ * @returns Subagent details or undefined if not found
1438+ */
1439+ getSubagentDetails ( id : string ) : {
1440+ prompt ?: string ;
1441+ result ?: string ;
1442+ spawnedAt : string ;
1443+ endedAt ?: string ;
1444+ childIds : string [ ] ;
1445+ } | undefined {
1446+ const state = this . subagentParser . getSubagent ( id ) ;
1447+ if ( ! state ) return undefined ;
1448+ return {
1449+ prompt : state . prompt ,
1450+ result : state . result ,
1451+ spawnedAt : state . spawnedAt ,
1452+ endedAt : state . endedAt ,
1453+ childIds : state . childIds ,
1454+ } ;
1455+ }
1456+
1457+ /**
1458+ * Get the currently active subagent ID (deepest in the hierarchy).
1459+ * Returns undefined if no subagent is currently active.
1460+ */
1461+ getActiveSubagentId ( ) : string | undefined {
1462+ const stack = this . subagentParser . getActiveStack ( ) ;
1463+ return stack . length > 0 ? stack [ 0 ] : undefined ;
1464+ }
1465+
13741466 /**
13751467 * Get the subagent tree for TUI rendering.
13761468 * Returns an array of root-level subagent tree nodes with their children nested.
0 commit comments