Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions cmd/entire/cli/agent/claudecode/transcript.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,17 @@ func ExtractSpawnedAgentIDs(transcript []TranscriptLine) map[string]string {
return agentIDs
}

// extractAllSpawnedAgentIDs parses the full transcript from the beginning to find
// all spawned agent IDs, regardless of startLine offset. This ensures subagents
// spawned before a checkpoint boundary are still discovered.
func extractAllSpawnedAgentIDs(transcriptData []byte) map[string]string {
fullParsed, err := transcript.ParseFromBytes(transcriptData)
if err != nil {
return nil
}
return ExtractSpawnedAgentIDs(fullParsed)
}

// extractAgentIDFromText extracts an agent ID from text containing "agentId: <id>".
func extractAgentIDFromText(text string) string {
const prefix = "agentId: "
Expand Down Expand Up @@ -305,8 +316,10 @@ func (c *ClaudeCodeAgent) CalculateTotalTokenUsage(transcriptData []byte, startL
// Calculate token usage from parsed transcript
mainUsage := CalculateTokenUsage(parsed)

// Extract spawned agent IDs from the same parsed transcript
agentIDs := ExtractSpawnedAgentIDs(parsed)
// Extract spawned agent IDs from the FULL transcript (not just startLine).
// Subagents spawned before startLine may still be active and writing to their
// transcript files, so we need to discover them from the beginning.
agentIDs := extractAllSpawnedAgentIDs(transcriptData)

// Calculate subagent token usage (skip when subagentsDir is empty to avoid reading from cwd)
if len(agentIDs) > 0 && subagentsDir != "" {
Expand Down Expand Up @@ -359,8 +372,10 @@ func (c *ClaudeCodeAgent) ExtractAllModifiedFiles(transcriptData []byte, startLi
}
}

// Find spawned subagents and collect their modified files (skip when subagentsDir is empty to avoid reading from cwd)
agentIDs := ExtractSpawnedAgentIDs(parsed)
// Find spawned subagents from the FULL transcript (not just startLine).
// Subagents spawned before startLine may still be active and writing files,
// so we need to discover them from the beginning.
agentIDs := extractAllSpawnedAgentIDs(transcriptData)
if subagentsDir == "" {
return files, nil
}
Expand Down
36 changes: 30 additions & 6 deletions cmd/entire/cli/agent/factoryaidroid/transcript.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,16 @@ func extractAgentIDFromText(text string) string {
return ""
}

// extractAllSpawnedAgentIDsFromBytes parses the full transcript from the beginning to find
// all spawned agent IDs, regardless of startLine offset.
func extractAllSpawnedAgentIDsFromBytes(data []byte) map[string]string {
fullParsed, _, err := ParseDroidTranscriptFromBytes(data, 0)
if err != nil {
return nil
}
return ExtractSpawnedAgentIDs(fullParsed)
}

// CalculateTotalTokenUsageFromBytes calculates token usage from pre-loaded transcript bytes,
// including subagents. It parses the main transcript bytes from startLine, extracts spawned
// agent IDs, and calculates their token usage from transcript files in subagentsDir.
Expand All @@ -362,7 +372,10 @@ func CalculateTotalTokenUsageFromBytes(data []byte, startLine int, subagentsDir

mainUsage := CalculateTokenUsage(parsed)

agentIDs := ExtractSpawnedAgentIDs(parsed)
// Extract spawned agent IDs from the FULL transcript (not just startLine).
// Subagents spawned before startLine may still be active and writing to their
// transcript files, so we need to discover them from the beginning.
agentIDs := extractAllSpawnedAgentIDsFromBytes(data)
if len(agentIDs) > 0 && subagentsDir != "" {
subagentUsage := &agent.TokenUsage{}
for agentID := range agentIDs {
Expand Down Expand Up @@ -405,7 +418,8 @@ func ExtractAllModifiedFilesFromBytes(data []byte, startLine int, subagentsDir s
fileSet[f] = true
}

agentIDs := ExtractSpawnedAgentIDs(parsed)
// Extract spawned agent IDs from the FULL transcript (not just startLine).
agentIDs := extractAllSpawnedAgentIDsFromBytes(data)
if subagentsDir == "" {
return files, nil
}
Expand Down Expand Up @@ -443,8 +457,13 @@ func CalculateTotalTokenUsageFromTranscript(transcriptPath string, startLine int
// Calculate token usage from parsed transcript
mainUsage := CalculateTokenUsage(parsed)

// Extract spawned agent IDs from the same parsed transcript
agentIDs := ExtractSpawnedAgentIDs(parsed)
// Extract spawned agent IDs from the FULL transcript (not just startLine).
// Subagents spawned before startLine may still be active.
allParsed, _, parseAllErr := ParseDroidTranscript(transcriptPath, 0)
var agentIDs map[string]string
if parseAllErr == nil {
agentIDs = ExtractSpawnedAgentIDs(allParsed)
}

// Calculate subagent token usage
if len(agentIDs) > 0 {
Expand Down Expand Up @@ -493,8 +512,13 @@ func ExtractAllModifiedFilesFromTranscript(transcriptPath string, startLine int,
fileSet[f] = true
}

// Find spawned subagents and collect their modified files
agentIDs := ExtractSpawnedAgentIDs(parsed)
// Find spawned subagents from the FULL transcript (not just startLine).
// Subagents spawned before startLine may still be active and writing files.
allParsed, _, parseAllErr := ParseDroidTranscript(transcriptPath, 0)
var agentIDs map[string]string
if parseAllErr == nil {
agentIDs = ExtractSpawnedAgentIDs(allParsed)
}
for agentID := range agentIDs {
agentPath := filepath.Join(subagentsDir, fmt.Sprintf("agent-%s.jsonl", agentID))
agentLines, _, agentErr := ParseDroidTranscript(agentPath, 0)
Expand Down
Loading