mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-11 01:25:03 +00:00
fix(parser): range-filter calls inside turns instead of dropping day-spanning turns
parseProviderSources keyed the dateRange check on a turn's first call timestamp, so a long autonomous turn starting before midnight was excluded from the next day's view entirely and every post-midnight call in it was lost. Filter calls inside the turn instead and keep the turn when any call falls in range. Fixes #852
This commit is contained in:
parent
85781999a5
commit
8b83ded657
2 changed files with 59 additions and 10 deletions
|
|
@ -2987,17 +2987,22 @@ async function parseProviderSources(
|
|||
const cachedFile = section.files[source.path]
|
||||
if (!cachedFile) continue
|
||||
|
||||
for (const turn of cachedFile.turns) {
|
||||
for (let turn of cachedFile.turns) {
|
||||
const hasDup = turn.calls.some(c => seenKeys.has(c.deduplicationKey))
|
||||
if (hasDup) continue
|
||||
|
||||
for (const c of turn.calls) seenKeys.add(c.deduplicationKey)
|
||||
|
||||
if (dateRange) {
|
||||
const callTs = turn.calls[0]?.timestamp
|
||||
if (!callTs) continue
|
||||
const ts = new Date(callTs)
|
||||
if (ts < dateRange.start || ts > dateRange.end) continue
|
||||
// Filter per call, not by the turn's first call: a long autonomous turn
|
||||
// can span midnight, so dropping the whole turn on its first timestamp
|
||||
// loses every in-range call made after it.
|
||||
const inRangeCalls = turn.calls.filter(c => {
|
||||
const ts = new Date(c.timestamp).getTime()
|
||||
return !isNaN(ts) && ts >= dateRange.start.getTime() && ts <= dateRange.end.getTime()
|
||||
})
|
||||
if (inRangeCalls.length === 0) continue
|
||||
turn = { ...turn, calls: inRangeCalls }
|
||||
}
|
||||
|
||||
const classified = cachedTurnToClassified(turn)
|
||||
|
|
@ -3036,17 +3041,22 @@ async function parseProviderSources(
|
|||
for (const [cachedPath, cachedFile] of Object.entries(section.files)) {
|
||||
if (allDiscoveredFiles.has(cachedPath)) continue // already counted above
|
||||
|
||||
for (const turn of cachedFile.turns) {
|
||||
for (let turn of cachedFile.turns) {
|
||||
const hasDup = turn.calls.some(c => seenKeys.has(c.deduplicationKey))
|
||||
if (hasDup) continue
|
||||
|
||||
for (const c of turn.calls) seenKeys.add(c.deduplicationKey)
|
||||
|
||||
if (dateRange) {
|
||||
const callTs = turn.calls[0]?.timestamp
|
||||
if (!callTs) continue
|
||||
const ts = new Date(callTs)
|
||||
if (ts < dateRange.start || ts > dateRange.end) continue
|
||||
// Filter per call, not by the turn's first call: a long autonomous turn
|
||||
// can span midnight, so dropping the whole turn on its first timestamp
|
||||
// loses every in-range call made after it.
|
||||
const inRangeCalls = turn.calls.filter(c => {
|
||||
const ts = new Date(c.timestamp).getTime()
|
||||
return !isNaN(ts) && ts >= dateRange.start.getTime() && ts <= dateRange.end.getTime()
|
||||
})
|
||||
if (inRangeCalls.length === 0) continue
|
||||
turn = { ...turn, calls: inRangeCalls }
|
||||
}
|
||||
|
||||
const classified = cachedTurnToClassified(turn)
|
||||
|
|
|
|||
|
|
@ -197,3 +197,42 @@ describe('provider turn grouping', () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('provider turn range filtering', () => {
|
||||
it('keeps the in-range calls of a codex turn that spans midnight instead of dropping the whole turn', async () => {
|
||||
// Regression test for #852: the range filter keyed on the turn's FIRST
|
||||
// call timestamp, so a long autonomous turn starting 23:59 the previous
|
||||
// day was excluded from the next day's view entirely, losing every
|
||||
// post-midnight call. One turn (t1) here has two token_count events
|
||||
// straddling midnight; only the post-midnight call may survive.
|
||||
const codexHome = join(home, 'codex')
|
||||
const sessionDir = join(codexHome, 'sessions', '2026', '05', '15')
|
||||
await mkdir(sessionDir, { recursive: true })
|
||||
const lines = [
|
||||
JSON.stringify({ type: 'session_meta', timestamp: '2026-05-15T23:55:00Z', payload: { session_id: 'sess-span', model: 'gpt-5.5', cwd: '/Users/test/project-a', originator: 'codex_cli_rs' } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-05-15T23:57:00Z', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: 'run the long task' }] } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-05-15T23:58:00Z', payload: { type: 'function_call', name: 'exec_command', arguments: JSON.stringify({ command: 'npm test' }) } }),
|
||||
JSON.stringify({ type: 'event_msg', timestamp: '2026-05-15T23:59:00Z', payload: { type: 'token_count', info: { last_token_usage: { input_tokens: 100, output_tokens: 30 }, total_token_usage: { total_tokens: 130 } } } }),
|
||||
JSON.stringify({ type: 'response_item', timestamp: '2026-05-16T00:10:00Z', payload: { type: 'function_call', name: 'exec_command', arguments: JSON.stringify({ command: 'npm run build' }) } }),
|
||||
JSON.stringify({ type: 'event_msg', timestamp: '2026-05-16T00:15:00Z', payload: { type: 'token_count', info: { last_token_usage: { input_tokens: 80, output_tokens: 20 }, total_token_usage: { total_tokens: 230 } } } }),
|
||||
]
|
||||
await writeFile(join(sessionDir, 'rollout-span.jsonl'), lines.join('\n') + '\n')
|
||||
|
||||
process.env['CODEX_HOME'] = codexHome
|
||||
try {
|
||||
const parseAllSessions = await loadParser()
|
||||
const projects = await parseAllSessions(dayRange(), 'codex')
|
||||
const session = projects[0]!.sessions[0]!
|
||||
const turn = session.turns[0]!
|
||||
|
||||
expect(session.turns).toHaveLength(1)
|
||||
expect(turn.assistantCalls.map(call => new Date(call.timestamp).toISOString())).toEqual([
|
||||
'2026-05-16T00:15:00.000Z',
|
||||
])
|
||||
expect(session.totalInputTokens).toBe(80)
|
||||
expect(session.totalOutputTokens).toBe(20)
|
||||
} finally {
|
||||
delete process.env['CODEX_HOME']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue