feat: separate system task token usage and add usage refresh

Add a System tasks tab to run history that splits background usage (supervisor, auto-supervision, magic notes, knowledge base, delegation) from conversation usage, with a manual refresh button and automatic refresh when usage is recorded. Record previously dropped usage from supervisor reviews, image regeneration, knowledge graph extraction, and remote embeddings.
This commit is contained in:
mesalogo
2026-09-29 10:38:37 +08:00
parent 3c9229ac7a
commit 9a9170441e
24 changed files with 991 additions and 286 deletions
+53 -2
View File
@@ -86,6 +86,7 @@ import {
import { FeedbackService } from './feedback/feedback-service'
import { registerFeedbackIpcHandler } from './feedback/feedback-ipc'
import type { AgentRuntimeSelection } from '../shared/runtime-selection-contracts'
import type { SystemModelUsageInput } from '../shared/assistant-contracts'
import {
runCleanupBeforeDeadline,
settleCleanupPhases
@@ -237,6 +238,28 @@ let managedRemoteExecutionServices:
let directModelSubagentScheduler: SubagentScheduler | undefined
let localToolEnvironmentService: LocalToolEnvironmentService | undefined
function recordKnowledgeUsage(
bucket: SystemModelUsageInput['bucket'],
usage: Omit<SystemModelUsageInput, 'source' | 'bucket'>
): void {
try {
assistantDatabase?.recordSystemModelUsage({
...usage,
source: 'knowledge',
bucket
})
} catch (error) {
void desktopDiagnostics
.recordFailure({
component: 'desktop',
stage: 'knowledge',
code: 'knowledge.usage.persist-failed',
error
})
.catch(() => undefined)
}
}
type ManagedEmbeddingProvider = EmbeddingProvider & {
dispose?: () => void | Promise<void>
}
@@ -259,7 +282,17 @@ async function createEmbeddingProvider(
return new OpenAIEmbeddingClient({
endpoint: connection.baseUrl,
model: connection.modelName,
apiKey: connection.apiKey
apiKey: connection.apiKey,
onUsage: ({ model, inputTokens }) =>
recordKnowledgeUsage('embedding', {
runtime: 'embedding',
provider: 'openai-compatible',
model,
input: inputTokens,
output: 0,
cacheRead: 0,
cacheWrite: 0
})
})
}
const snapshot = await manager.getSnapshot()
@@ -899,7 +932,16 @@ if (hasSingleInstanceLock) {
credentialCipher: secureCipher,
databasePath: join(app.getPath('userData'), 'knowledge.sqlite'),
managedRoot: join(app.getPath('userData'), 'knowledge'),
extractStructured: createModelGraphExtractor(settingsStore),
extractStructured: createModelGraphExtractor(
settingsStore,
fetch,
undefined,
(usage) =>
recordKnowledgeUsage('graph-extraction', {
runtime: 'model',
...usage
})
),
parseDocument: documentParsingService.parse
})
knowledgeService = startupKnowledgeService
@@ -935,6 +977,15 @@ if (hasSingleInstanceLock) {
)
}
})
},
onModelUsageChanged: () => {
if (
mainWindow &&
!mainWindow.isDestroyed() &&
!mainWindow.webContents.isDestroyed()
) {
mainWindow.webContents.send(ipcChannels.tokenUsageChanged)
}
}
}
)