mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
Run local usage services in a supervised backend process (#40792)
* Move local usage services into a supervised backend process * Own usage recovery and worker retirement lifetimes * Preserve answered usage data during background refresh * Run independent usage scans concurrently in the backend * Limit backend startup paths to the delegated usage service * Show usage failures through backend recovery * Include usage failure strings in the English catalog * Keep usage update details beside refresh failures * Resolve usage freshness failure from the final producer snapshot * Retain the forced refresh completion requirement * Keep managed backend failures out of application crash reports * Preserve usage recovery boundaries across backend reconnects * Match the backend RPC cancellation signal in the usage seam proof * Avoid serializing backend requests before port delivery * Keep receiving request validation in the boundary schemas * test: align usage transport coverage with renderer project * fix: preserve unknown usage state and renderer retirement * fix(usage): revalidate tracking before scan admission * fix(usage): retry failed backend startup on demand * test(usage): isolate stale backend ownership checks
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { mkdtempSync, rmSync } from 'node:fs'
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, vi } from 'vitest'
|
||||
import type { Mock } from 'vitest'
|
||||
import { createTestUsageStoreHost } from '../usage/usage-store-host-test-fixture'
|
||||
import { scanCodexUsageFilesViaWorker } from '../usage/scan-worker/usage-scan-worker-spawn'
|
||||
import { CodexUsageStore, initCodexUsagePath } from './store'
|
||||
import { CodexUsageStore } from './store'
|
||||
import type { CodexUsagePersistedState } from './types'
|
||||
|
||||
export function createEmptyScanResult() {
|
||||
@@ -66,39 +66,33 @@ export function createWorktreeUsageSession(worktreeId: string) {
|
||||
}
|
||||
|
||||
export function createStoreWithState(state: Partial<CodexUsagePersistedState>): CodexUsageStore {
|
||||
const store = new CodexUsageStore({
|
||||
getRepos: () => [],
|
||||
getAllWorktreeMeta: () => ({}),
|
||||
getRememberedWorktreeDisplayNames: () => new Map(),
|
||||
whenLoaded: async () => {}
|
||||
})
|
||||
|
||||
;(store as unknown as { state: CodexUsagePersistedState }).state = {
|
||||
schemaVersion: 1,
|
||||
worktreeFingerprint: null,
|
||||
processedFiles: [],
|
||||
sessions: [],
|
||||
dailyAggregates: [],
|
||||
scanState: {
|
||||
enabled: false,
|
||||
lastScanStartedAt: null,
|
||||
lastScanCompletedAt: null,
|
||||
lastScanError: null
|
||||
},
|
||||
...state
|
||||
}
|
||||
|
||||
return store
|
||||
writeFileSync(
|
||||
join(activeUserData, 'orca-codex-usage.json'),
|
||||
JSON.stringify({
|
||||
schemaVersion: 7,
|
||||
worktreeFingerprint: null,
|
||||
processedFiles: [],
|
||||
sessions: [],
|
||||
dailyAggregates: [],
|
||||
scanState: {
|
||||
enabled: false,
|
||||
lastScanStartedAt: null,
|
||||
lastScanCompletedAt: null,
|
||||
lastScanError: null
|
||||
},
|
||||
...state
|
||||
})
|
||||
)
|
||||
return new CodexUsageStore(createTestUsageStoreHost({ userDataPath: activeUserData }))
|
||||
}
|
||||
|
||||
/** The caller owns the hoisted Electron mock. */
|
||||
export function setupCodexUsageStoreEnv(getPathMock: Mock): { tempUserData: string } {
|
||||
let activeUserData: string
|
||||
export function setupCodexUsageStoreEnv(): { tempUserData: string } {
|
||||
const env = { tempUserData: '' }
|
||||
|
||||
beforeEach(() => {
|
||||
env.tempUserData = mkdtempSync(join(tmpdir(), 'orca-codex-usage-store-'))
|
||||
getPathMock.mockReturnValue(env.tempUserData)
|
||||
initCodexUsagePath()
|
||||
activeUserData = env.tempUserData
|
||||
vi.mocked(scanCodexUsageFilesViaWorker).mockReset()
|
||||
vi.mocked(scanCodexUsageFilesViaWorker).mockResolvedValue(createEmptyScanResult())
|
||||
vi.useFakeTimers()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { codexOpenCodeTokenSessions } from '../usage/reporting/token-usage/agent-token-usage'
|
||||
import { app } from 'electron'
|
||||
import { join } from 'node:path'
|
||||
import type {
|
||||
CodexUsageBreakdownKind,
|
||||
@@ -12,7 +11,7 @@ import type {
|
||||
CodexUsageSummary
|
||||
} from '../../shared/codex-usage-types'
|
||||
import type { AutomationRunUsage } from '../../shared/automations/history/automation-run'
|
||||
import type { UsageWorktreeSource } from '../usage/worktree-attribution/usage-worktree-metadata'
|
||||
import type { UsageStoreHost } from '../usage/usage-store-host'
|
||||
import type { CodexUsagePersistedState } from './types'
|
||||
import type { AutomationUsageLookupInput } from './codex-automation-run-attribution'
|
||||
import { CODEX_USAGE_SCHEMA_VERSION, codexUsageProvider } from './codex-usage-provider'
|
||||
@@ -24,8 +23,6 @@ import { splitUsageCacheFileViaWorker } from '../usage/scan-worker/usage-scan-wo
|
||||
|
||||
const SCHEMA_VERSION = CODEX_USAGE_SCHEMA_VERSION
|
||||
|
||||
let _codexUsageFile: string | null = null
|
||||
|
||||
function getDefaultState(): CodexUsagePersistedState {
|
||||
return {
|
||||
schemaVersion: SCHEMA_VERSION,
|
||||
@@ -63,30 +60,19 @@ export function normalizePersistedState(state: CodexUsagePersistedState): CodexU
|
||||
}
|
||||
}
|
||||
|
||||
export function initCodexUsagePath(): void {
|
||||
_codexUsageFile = join(app.getPath('userData'), 'orca-codex-usage.json')
|
||||
}
|
||||
|
||||
function getCodexUsageFile(): string {
|
||||
if (!_codexUsageFile) {
|
||||
_codexUsageFile = join(app.getPath('userData'), 'orca-codex-usage.json')
|
||||
}
|
||||
return _codexUsageFile
|
||||
}
|
||||
|
||||
export class CodexUsageStore extends UsageProviderStoreLifecycle<
|
||||
'processedFiles',
|
||||
CodexUsagePersistedState,
|
||||
'hasAnyCodexData'
|
||||
> {
|
||||
constructor(store: UsageWorktreeSource) {
|
||||
super(store, {
|
||||
constructor(host: UsageStoreHost) {
|
||||
super(host, {
|
||||
tokenUsage: {
|
||||
provider: 'codex',
|
||||
selectSessions: (state) => codexOpenCodeTokenSessions(state.sessions)
|
||||
},
|
||||
logTag: '[codex-usage]',
|
||||
resolveCacheFile: getCodexUsageFile,
|
||||
resolveCacheFile: () => join(host.userDataPath, 'orca-codex-usage.json'),
|
||||
createDefaultState: getDefaultState,
|
||||
normalizeState: normalizePersistedState,
|
||||
sourceKey: 'processedFiles',
|
||||
|
||||
Reference in New Issue
Block a user