mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 00:03:20 +00:00
Fix cross-tool usage double-counting for fork/resume-copied history
- Widen Claude dedupe keys with message-id and uuid fallbacks so forks missing requestId still dedupe correctly, and drop Codex's sessionId from event keys since fork/resume rewrites session_meta.id while copying identical token_count records. - Track hasDeferredClaims per cached file/database across Claude, Codex, and OpenCode scanners so that when an owning file is deleted, only files that deferred a claim need reparsing to reclaim those turns instead of rescanning the entire corpus. - Let OpenCode's live opencode.db reclaim sessions from a stale backup claim once it reappears, avoiding a frozen stale snapshot. - Bump schema versions to invalidate caches built with the old, narrower ownership keys (#8006, #8013 follow-up).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
|
||||
import { mkdirSync, mkdtempSync, rmSync, unlinkSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
@@ -475,4 +475,59 @@ describe('scanOpenCodeUsageDatabases', () => {
|
||||
expect(sessionOne?.totalInputTokens).toBe(1000)
|
||||
expect(sessionOne?.eventCount).toBe(1)
|
||||
})
|
||||
|
||||
it('lets the live database reclaim sessions after a sticky backup-only claim', async () => {
|
||||
// First scan only has the backup (e.g. live db temporarily missing), so it
|
||||
// owns session-1 at the stale snapshot. When opencode.db reappears with
|
||||
// higher totals it must reclaim the session instead of staying frozen.
|
||||
writeSessionTotalsDb('opencode-backup.db', [['session-1', 400]])
|
||||
|
||||
const first = await scanOpenCodeUsageDatabases([], [])
|
||||
expect(
|
||||
first.dailyAggregates.reduce((total, aggregate) => total + aggregate.inputTokens, 0)
|
||||
).toBe(400)
|
||||
expect(first.processedDatabases[0]?.ownedSessionIds).toEqual(['session-1'])
|
||||
|
||||
writeSessionTotalsDb('opencode.db', [
|
||||
['session-1', 1000],
|
||||
['session-2', 200]
|
||||
])
|
||||
|
||||
const second = await scanOpenCodeUsageDatabases([], first.processedDatabases)
|
||||
expect(
|
||||
second.dailyAggregates.reduce((total, aggregate) => total + aggregate.inputTokens, 0)
|
||||
).toBe(1200)
|
||||
const sessionOne = second.sessions.find((session) => session.sessionId === 'session-1')
|
||||
expect(sessionOne?.totalInputTokens).toBe(1000)
|
||||
expect(sessionOne?.eventCount).toBe(1)
|
||||
})
|
||||
|
||||
it('reclaims sessions when the owning live database is deleted', async () => {
|
||||
const canonicalPath = writeSessionTotalsDb('opencode.db', [
|
||||
['session-1', 1000],
|
||||
['session-2', 200]
|
||||
])
|
||||
writeSessionTotalsDb('opencode-backup.db', [
|
||||
['session-1', 400],
|
||||
['session-9', 50]
|
||||
])
|
||||
|
||||
const first = await scanOpenCodeUsageDatabases([], [])
|
||||
expect(
|
||||
first.dailyAggregates.reduce((total, aggregate) => total + aggregate.inputTokens, 0)
|
||||
).toBe(1250)
|
||||
|
||||
unlinkSync(canonicalPath)
|
||||
|
||||
const second = await scanOpenCodeUsageDatabases([], first.processedDatabases)
|
||||
expect(
|
||||
second.dailyAggregates.reduce((total, aggregate) => total + aggregate.inputTokens, 0)
|
||||
).toBe(450)
|
||||
expect(second.sessions.map((session) => session.sessionId).sort()).toEqual([
|
||||
'session-1',
|
||||
'session-9'
|
||||
])
|
||||
const sessionOne = second.sessions.find((session) => session.sessionId === 'session-1')
|
||||
expect(sessionOne?.totalInputTokens).toBe(400)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user