@tonyclaw/agent-inspector 4.0.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/backend/nitro.json +1 -1
- package/.output/cli.js +192 -109
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-CTHqmroT.mjs → router-BBY_zWRK.mjs} +91 -3
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-L3n4J-Or.js → CompareDrawer-rtNElN-B.js} +1 -1
- package/.output/ui/assets/{InspectorPet-D5o_3OZS.js → InspectorPet-QaAiuF3d.js} +1 -1
- package/.output/ui/assets/{ProxyViewerContainer-Ocagx75S.js → ProxyViewerContainer-6twNZHwl.js} +4 -4
- package/.output/ui/assets/{ReplayDialog-Bk7x1h-y.js → ReplayDialog-B7KlziO2.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-U4lzL7Sc.js → RequestAnatomy-2XVFBrW4.js} +1 -1
- package/.output/ui/assets/{ResponseView-LOWfvBLK.js → ResponseView-BULgzLVB.js} +1 -1
- package/.output/ui/assets/{StreamingChunkSequence-DPfsrrXQ.js → StreamingChunkSequence-DSkvkbns.js} +1 -1
- package/.output/ui/assets/{_sessionId-OdX2Jvt1.js → _sessionId-D2dmJi3G.js} +1 -1
- package/.output/ui/assets/{_sessionId-0MlWS-CO.js → _sessionId-DcS0s1Mr.js} +1 -1
- package/.output/ui/assets/{index-Du4qNOOt.js → index-B0TN1KKO.js} +2 -2
- package/.output/ui/assets/{index-gvFj_9K6.js → index-Bva1zHEs.js} +1 -1
- package/.output/ui/assets/{index-Ea-c8hPg.js → index-BzBExOxu.js} +1 -1
- package/.output/ui/assets/{index-zVVS53Hd.js → index-k6sRQz55.js} +1 -1
- package/.output/ui/assets/{json-viewer-C1WAMzg3.js → json-viewer-CWM4tY-j.js} +1 -1
- package/.output/ui/assets/{jszip.min-B8AKu2zm.js → jszip.min-BkWvRFlo.js} +1 -1
- package/.output/ui/index.html +1 -1
- package/package.json +5 -10
- package/src/assets/IDE/claudecode-color.svg +0 -1
- package/src/assets/IDE/codex-color.svg +0 -1
- package/src/assets/IDE/opencode.svg +0 -1
- package/src/assets/IDE/xiaomimimo.svg +0 -1
- package/src/assets/favicon.svg +0 -21
- package/src/assets/logos/alibaba.svg +0 -1
- package/src/assets/logos/anthropic.svg +0 -1
- package/src/assets/logos/claude-code.svg +0 -4
- package/src/assets/logos/deepseek.svg +0 -1
- package/src/assets/logos/minimax.jpeg +0 -0
- package/src/assets/logos/openai.svg +0 -1
- package/src/assets/logos/opencode.svg +0 -4
- package/src/assets/logos/qwen.webp +0 -0
- package/src/assets/logos/zhipuai.svg +0 -219
- package/src/backend/router.tsx +0 -36
- package/src/backend/routes/$.ts +0 -26
- package/src/backend/routes/__root.ts +0 -4
- package/src/backend/routes/api/-instances.ts +0 -7
- package/src/backend/routes/api/alerts.summary.ts +0 -28
- package/src/backend/routes/api/alerts.ts +0 -70
- package/src/backend/routes/api/config.paths.ts +0 -14
- package/src/backend/routes/api/config.ts +0 -73
- package/src/backend/routes/api/ecosystem.packages.$packageId.help.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.install.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.runner-presets.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.$packageId.upgrade.ts +0 -11
- package/src/backend/routes/api/ecosystem.packages.ts +0 -21
- package/src/backend/routes/api/ecosystem.recipes.$recipeId.run.ts +0 -11
- package/src/backend/routes/api/ecosystem.tasks.$taskId.ts +0 -16
- package/src/backend/routes/api/ecosystem.tasks.ts +0 -12
- package/src/backend/routes/api/groups.$groupId.evidence.ts +0 -55
- package/src/backend/routes/api/groups.$groupId.sessions.ts +0 -50
- package/src/backend/routes/api/groups.$groupId.ts +0 -93
- package/src/backend/routes/api/groups.ts +0 -79
- package/src/backend/routes/api/health.ts +0 -22
- package/src/backend/routes/api/instances.$name.connection.ts +0 -12
- package/src/backend/routes/api/instances.$name.restart.ts +0 -23
- package/src/backend/routes/api/instances.$name.start.ts +0 -18
- package/src/backend/routes/api/instances.$name.stop.ts +0 -23
- package/src/backend/routes/api/instances.$name.ts +0 -12
- package/src/backend/routes/api/instances.ts +0 -16
- package/src/backend/routes/api/knowledge.candidates.$candidateId.promote.ts +0 -32
- package/src/backend/routes/api/knowledge.candidates.$candidateId.ts +0 -50
- package/src/backend/routes/api/knowledge.candidates.ts +0 -10
- package/src/backend/routes/api/knowledge.project-context.ts +0 -29
- package/src/backend/routes/api/knowledge.search.ts +0 -62
- package/src/backend/routes/api/knowledge.sessions.$sessionId.candidates.ts +0 -26
- package/src/backend/routes/api/logs.$id.body.ts +0 -58
- package/src/backend/routes/api/logs.$id.chunks.ts +0 -37
- package/src/backend/routes/api/logs.$id.replay.ts +0 -297
- package/src/backend/routes/api/logs.$id.ts +0 -23
- package/src/backend/routes/api/logs.import.ts +0 -39
- package/src/backend/routes/api/logs.stream.ts +0 -174
- package/src/backend/routes/api/logs.ts +0 -214
- package/src/backend/routes/api/mcp.ts +0 -25
- package/src/backend/routes/api/models.ts +0 -10
- package/src/backend/routes/api/pi-agent.ts +0 -29
- package/src/backend/routes/api/providers.$providerId.model-metadata.ts +0 -173
- package/src/backend/routes/api/providers.$providerId.test.log.ts +0 -342
- package/src/backend/routes/api/providers.$providerId.ts +0 -111
- package/src/backend/routes/api/providers.export.ts +0 -44
- package/src/backend/routes/api/providers.import.ts +0 -81
- package/src/backend/routes/api/providers.scan.ts +0 -28
- package/src/backend/routes/api/providers.ts +0 -102
- package/src/backend/routes/api/runs.$runId.evidence.ts +0 -55
- package/src/backend/routes/api/runs.$runId.ts +0 -50
- package/src/backend/routes/api/runs.ts +0 -62
- package/src/backend/routes/api/runtime.ts +0 -46
- package/src/backend/routes/api/sessions.ts +0 -51
- package/src/backend/routes/api/storage.ts +0 -56
- package/src/backend/routes/livez.ts +0 -13
- package/src/backend/routes/metrics.ts +0 -14
- package/src/backend/routes/proxy/$.ts +0 -15
- package/src/backend/routes/readyz.ts +0 -18
- package/src/backend/start.ts +0 -10
- package/src/cli/alias.ts +0 -431
- package/src/cli/detect-tools.ts +0 -123
- package/src/cli/doctor.ts +0 -836
- package/src/cli/instance.ts +0 -1071
- package/src/cli/instanceApi.ts +0 -213
- package/src/cli/instanceArgs.ts +0 -301
- package/src/cli/instanceControl.ts +0 -191
- package/src/cli/instanceModel.ts +0 -276
- package/src/cli/instanceRegistry.ts +0 -731
- package/src/cli/networkHints.ts +0 -170
- package/src/cli/onboard.ts +0 -1668
- package/src/cli/onboardTarget.ts +0 -228
- package/src/cli/rustBackendPackage.ts +0 -458
- package/src/cli/startupOutput.ts +0 -16
- package/src/cli/templates/codex-skill-onboard.ts +0 -320
- package/src/cli/templates/command-onboard.ts +0 -25
- package/src/cli/templates/skill-onboard.ts +0 -625
- package/src/cli.ts +0 -1433
- package/src/components/OnboardingBanner.tsx +0 -115
- package/src/components/ProxyViewer.tsx +0 -2259
- package/src/components/ProxyViewerContainer.tsx +0 -1076
- package/src/components/alerts/AlertsDialog.tsx +0 -615
- package/src/components/clients/ClientLogo.tsx +0 -175
- package/src/components/ecosystem/AgentLabDialog.tsx +0 -947
- package/src/components/errors/SafeErrorBoundary.tsx +0 -201
- package/src/components/groups/GroupsDialog.tsx +0 -1304
- package/src/components/inspector-pet/InspectorPet.tsx +0 -398
- package/src/components/inspector-pet/InspectorPetCrab3D.tsx +0 -455
- package/src/components/inspector-pet/inspectorPetLogic.ts +0 -272
- package/src/components/pi-agent/PiAgentPanel.tsx +0 -446
- package/src/components/pi-agent/piAgentChatLogic.ts +0 -273
- package/src/components/providers/ImportWizardDialog.tsx +0 -281
- package/src/components/providers/ProviderCard.tsx +0 -347
- package/src/components/providers/ProviderForm.tsx +0 -1183
- package/src/components/providers/ProviderLogo.tsx +0 -122
- package/src/components/providers/ProviderTestResultsView.tsx +0 -734
- package/src/components/providers/ProvidersPanel.tsx +0 -714
- package/src/components/providers/SettingsDialog.tsx +0 -1042
- package/src/components/providers/providerFormCapabilities.ts +0 -36
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +0 -902
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +0 -51
- package/src/components/proxy-viewer/ApplicationBar.tsx +0 -123
- package/src/components/proxy-viewer/CompareDrawer.tsx +0 -984
- package/src/components/proxy-viewer/ConversationGroup.tsx +0 -208
- package/src/components/proxy-viewer/ConversationGroupList.tsx +0 -652
- package/src/components/proxy-viewer/ConversationHeader.tsx +0 -429
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +0 -186
- package/src/components/proxy-viewer/LogEntry.tsx +0 -1419
- package/src/components/proxy-viewer/LogEntryHeader.tsx +0 -596
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -57
- package/src/components/proxy-viewer/RawExportWarning.tsx +0 -77
- package/src/components/proxy-viewer/ReplayDialog.tsx +0 -636
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +0 -299
- package/src/components/proxy-viewer/ResponseView.tsx +0 -202
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +0 -61
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +0 -265
- package/src/components/proxy-viewer/ThreadConnector.tsx +0 -165
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +0 -64
- package/src/components/proxy-viewer/TurnGroup.tsx +0 -383
- package/src/components/proxy-viewer/TurnGroupList.tsx +0 -156
- package/src/components/proxy-viewer/accessibility.ts +0 -8
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +0 -887
- package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +0 -222
- package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +0 -737
- package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +0 -268
- package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +0 -53
- package/src/components/proxy-viewer/anatomy/types.ts +0 -41
- package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +0 -114
- package/src/components/proxy-viewer/bodyHydration.ts +0 -123
- package/src/components/proxy-viewer/cacheTrend.ts +0 -50
- package/src/components/proxy-viewer/diff/DiffView.tsx +0 -321
- package/src/components/proxy-viewer/diff/computeDiff.ts +0 -178
- package/src/components/proxy-viewer/diff/index.ts +0 -3
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +0 -194
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +0 -81
- package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +0 -21
- package/src/components/proxy-viewer/formats/index.tsx +0 -47
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +0 -442
- package/src/components/proxy-viewer/index.ts +0 -9
- package/src/components/proxy-viewer/lazy.ts +0 -41
- package/src/components/proxy-viewer/liveConnectionState.ts +0 -158
- package/src/components/proxy-viewer/log-formats/anthropic.ts +0 -191
- package/src/components/proxy-viewer/log-formats/index.ts +0 -23
- package/src/components/proxy-viewer/log-formats/openai.ts +0 -256
- package/src/components/proxy-viewer/log-formats/types.ts +0 -40
- package/src/components/proxy-viewer/log-formats/unknown.ts +0 -18
- package/src/components/proxy-viewer/logEntryVisibility.ts +0 -73
- package/src/components/proxy-viewer/logFocus.ts +0 -54
- package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +0 -124
- package/src/components/proxy-viewer/proxyViewerLogic.ts +0 -488
- package/src/components/proxy-viewer/replayComparison.ts +0 -131
- package/src/components/proxy-viewer/requestDiff.ts +0 -277
- package/src/components/proxy-viewer/requestTools.ts +0 -220
- package/src/components/proxy-viewer/useCopyFeedback.ts +0 -38
- package/src/components/proxy-viewer/useKeyboardNavigation.ts +0 -232
- package/src/components/proxy-viewer/viewerState.ts +0 -176
- package/src/components/ui/badge.tsx +0 -47
- package/src/components/ui/button.tsx +0 -47
- package/src/components/ui/collapsible.tsx +0 -21
- package/src/components/ui/confirm-dialog.tsx +0 -51
- package/src/components/ui/crab-logo.tsx +0 -45
- package/src/components/ui/crab-variants.tsx +0 -467
- package/src/components/ui/dialog.tsx +0 -130
- package/src/components/ui/icon-trigger.ts +0 -2
- package/src/components/ui/json-viewer-bulk.ts +0 -132
- package/src/components/ui/json-viewer.tsx +0 -508
- package/src/components/ui/scroll-area.tsx +0 -54
- package/src/components/ui/select.tsx +0 -178
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/tabs.tsx +0 -88
- package/src/components/ui/tooltip.tsx +0 -51
- package/src/components/ui/transient-toast.tsx +0 -105
- package/src/contracts/anthropic.ts +0 -230
- package/src/contracts/index.ts +0 -59
- package/src/contracts/json.ts +0 -37
- package/src/contracts/log.ts +0 -139
- package/src/contracts/openai.ts +0 -267
- package/src/index.css +0 -11
- package/src/knowledge/candidateStore.ts +0 -94
- package/src/knowledge/distiller.ts +0 -98
- package/src/knowledge/openclawClient.ts +0 -161
- package/src/knowledge/openclawGatewayClient.ts +0 -249
- package/src/knowledge/openclawMarkdown.ts +0 -146
- package/src/knowledge/redactor.ts +0 -80
- package/src/knowledge/types.ts +0 -83
- package/src/lib/alertContract.ts +0 -76
- package/src/lib/apiClient.ts +0 -166
- package/src/lib/apiFormat.ts +0 -29
- package/src/lib/backendImplementation.ts +0 -16
- package/src/lib/basePath.ts +0 -107
- package/src/lib/browserRuntimeContract.ts +0 -124
- package/src/lib/browserRuntimeResolver.ts +0 -333
- package/src/lib/chunkLoadRecovery.ts +0 -83
- package/src/lib/claudeCodeStrip.ts +0 -98
- package/src/lib/clipboard.ts +0 -67
- package/src/lib/codingAgentEndpoints.ts +0 -54
- package/src/lib/ecosystemContract.ts +0 -130
- package/src/lib/export-logs.ts +0 -287
- package/src/lib/groupContract.ts +0 -166
- package/src/lib/instanceContract.ts +0 -239
- package/src/lib/jsonFile.ts +0 -46
- package/src/lib/logImportContract.ts +0 -12
- package/src/lib/managedInstance.ts +0 -112
- package/src/lib/mask.ts +0 -4
- package/src/lib/objectUtils.ts +0 -22
- package/src/lib/piAgentContract.ts +0 -102
- package/src/lib/providerContract.ts +0 -125
- package/src/lib/providerImportContract.ts +0 -27
- package/src/lib/providerModelMetadata.ts +0 -458
- package/src/lib/providerTestContract.ts +0 -170
- package/src/lib/providerTestPrompt.ts +0 -100
- package/src/lib/publicBasePathContext.ts +0 -137
- package/src/lib/resourceLimits.ts +0 -345
- package/src/lib/runContract.ts +0 -164
- package/src/lib/runtimeConfig.ts +0 -43
- package/src/lib/safeDiagnostic.ts +0 -38
- package/src/lib/safeFetch.ts +0 -508
- package/src/lib/serverPort.ts +0 -47
- package/src/lib/sessionInfoContract.ts +0 -78
- package/src/lib/sessionUrl.ts +0 -46
- package/src/lib/ssrfGuard.ts +0 -133
- package/src/lib/stopReason.ts +0 -130
- package/src/lib/themeMode.ts +0 -41
- package/src/lib/timeDisplay.ts +0 -22
- package/src/lib/toolTrace.ts +0 -290
- package/src/lib/uiScale.ts +0 -117
- package/src/lib/unknown.ts +0 -27
- package/src/lib/upstreamUrl.ts +0 -150
- package/src/lib/useAlerts.ts +0 -82
- package/src/lib/useGroupEvidence.ts +0 -37
- package/src/lib/useGroups.ts +0 -32
- package/src/lib/useOnboarding.ts +0 -93
- package/src/lib/useProviders.ts +0 -30
- package/src/lib/useStripConfig.ts +0 -154
- package/src/lib/useThemeMode.ts +0 -40
- package/src/lib/useUiScale.ts +0 -52
- package/src/lib/utils.ts +0 -78
- package/src/mcp/currentContext.ts +0 -65
- package/src/mcp/instanceHandlers.ts +0 -151
- package/src/mcp/loopback.ts +0 -88
- package/src/mcp/mode.ts +0 -75
- package/src/mcp/previewExtractor.ts +0 -233
- package/src/mcp/server.ts +0 -2274
- package/src/mcp/toolHandlers.ts +0 -1533
- package/src/proxy/alerts.ts +0 -664
- package/src/proxy/chunkStorage.ts +0 -123
- package/src/proxy/config.ts +0 -218
- package/src/proxy/constants.ts +0 -66
- package/src/proxy/dataDir.ts +0 -147
- package/src/proxy/ecosystemExecutionConfirmation.ts +0 -114
- package/src/proxy/ecosystemExecutionRoute.ts +0 -116
- package/src/proxy/ecosystemRegistry.ts +0 -296
- package/src/proxy/ecosystemTasks.ts +0 -415
- package/src/proxy/evidenceAnalysis.ts +0 -526
- package/src/proxy/evidenceExporter.ts +0 -235
- package/src/proxy/formats/anthropic/anthropicProvider.ts +0 -75
- package/src/proxy/formats/anthropic/handler.ts +0 -75
- package/src/proxy/formats/anthropic/index.ts +0 -13
- package/src/proxy/formats/anthropic/register.ts +0 -4
- package/src/proxy/formats/anthropic/schemas.ts +0 -6
- package/src/proxy/formats/anthropic/stream.ts +0 -205
- package/src/proxy/formats/handler.ts +0 -46
- package/src/proxy/formats/index.ts +0 -13
- package/src/proxy/formats/jsonSchema.ts +0 -2
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -34
- package/src/proxy/formats/openai/codexMetadata.ts +0 -61
- package/src/proxy/formats/openai/handler.ts +0 -133
- package/src/proxy/formats/openai/index.ts +0 -33
- package/src/proxy/formats/openai/provider.ts +0 -50
- package/src/proxy/formats/openai/register.ts +0 -4
- package/src/proxy/formats/openai/schemas.ts +0 -25
- package/src/proxy/formats/openai/stream.ts +0 -600
- package/src/proxy/formats/openai/zhipuProvider.ts +0 -31
- package/src/proxy/formats/providerAdapter.ts +0 -50
- package/src/proxy/formats/providerRegistry.ts +0 -51
- package/src/proxy/formats/providers/index.ts +0 -3
- package/src/proxy/formats/registry.ts +0 -65
- package/src/proxy/groupEvidenceExporter.ts +0 -371
- package/src/proxy/groupStore.ts +0 -336
- package/src/proxy/handler.ts +0 -729
- package/src/proxy/identityProxy.ts +0 -1842
- package/src/proxy/jsonlRecovery.ts +0 -554
- package/src/proxy/logBodyChunks.ts +0 -91
- package/src/proxy/logFinalizer.ts +0 -777
- package/src/proxy/logFinalizer.worker.ts +0 -24
- package/src/proxy/logImportUpload.ts +0 -128
- package/src/proxy/logImporter.ts +0 -813
- package/src/proxy/logIndex.ts +0 -663
- package/src/proxy/logSearch.ts +0 -122
- package/src/proxy/logger.ts +0 -537
- package/src/proxy/openaiOrphanToolStrip.ts +0 -142
- package/src/proxy/platformCommands.ts +0 -258
- package/src/proxy/privateDataPath.ts +0 -419
- package/src/proxy/providerImporters.ts +0 -578
- package/src/proxy/providerScanStore.ts +0 -87
- package/src/proxy/providerSecretStore.ts +0 -216
- package/src/proxy/providers.ts +0 -998
- package/src/proxy/rawStreamCapture.ts +0 -342
- package/src/proxy/runFailures.ts +0 -94
- package/src/proxy/runStore.ts +0 -214
- package/src/proxy/runtimeAdmission.ts +0 -55
- package/src/proxy/runtimeHealth.ts +0 -221
- package/src/proxy/runtimeMetrics.ts +0 -149
- package/src/proxy/runtimeShutdown.ts +0 -157
- package/src/proxy/schemas.ts +0 -112
- package/src/proxy/sessionArchive.ts +0 -479
- package/src/proxy/sessionInfo.ts +0 -266
- package/src/proxy/sessionProcess.ts +0 -194
- package/src/proxy/sessionRuntime.ts +0 -121
- package/src/proxy/sessionSupervisor.ts +0 -305
- package/src/proxy/sessionWorkerEntry.ts +0 -26
- package/src/proxy/shutdownCoordinator.ts +0 -90
- package/src/proxy/socketTracker.ts +0 -639
- package/src/proxy/sqliteLogIndex.ts +0 -733
- package/src/proxy/storageLifecycle.ts +0 -432
- package/src/proxy/store.ts +0 -1773
- package/src/proxy/streamTiming.ts +0 -31
- package/src/proxy/toolSchemaWarnings.ts +0 -529
- package/src/proxy/uiAssetServer.ts +0 -106
- package/src/proxy/upstream.ts +0 -127
- package/src/services/alerts.ts +0 -12
- package/src/services/groups.ts +0 -119
- package/src/services/piAgent.ts +0 -421
- package/src/services/piAgentRetrieval.ts +0 -1180
- package/src/services/result.ts +0 -17
- package/src/services/runs.ts +0 -76
- package/src/ui/bootstrap.ts +0 -167
- package/src/ui/index.html +0 -38
- package/src/ui/logsStreamClient.ts +0 -20
- package/src/ui/main.tsx +0 -13
- package/src/ui/public/runtime-config.js +0 -2
- package/src/ui/router.tsx +0 -13
- package/src/ui/routes/__root.tsx +0 -69
- package/src/ui/routes/index.tsx +0 -35
- package/src/ui/routes/session/$sessionId.tsx +0 -50
- package/src/ui/runtimeDiscovery.ts +0 -50
package/src/proxy/logIndex.ts
DELETED
|
@@ -1,663 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { writeFile, readdir, rename, unlink, open } from "node:fs/promises";
|
|
3
|
-
import { constants, existsSync } from "node:fs";
|
|
4
|
-
import { join, dirname } from "node:path";
|
|
5
|
-
import { Buffer } from "node:buffer";
|
|
6
|
-
import { resolveLogDir, logger } from "./logger";
|
|
7
|
-
import {
|
|
8
|
-
CapturedLogSchema,
|
|
9
|
-
type ApiFormat,
|
|
10
|
-
type CaptureIncompleteReason,
|
|
11
|
-
type CapturedLog,
|
|
12
|
-
type StopReason,
|
|
13
|
-
type ToolTraceEvent,
|
|
14
|
-
} from "./schemas";
|
|
15
|
-
import { textByteLength } from "../lib/unknown";
|
|
16
|
-
import { extractToolTraceEvents } from "../lib/toolTrace";
|
|
17
|
-
import { extractStopReason } from "../lib/stopReason";
|
|
18
|
-
import {
|
|
19
|
-
getSqliteLogIndexMaxId,
|
|
20
|
-
listSqliteLogCursorEntries,
|
|
21
|
-
replaceSqliteLogIndexEntries,
|
|
22
|
-
syncSqliteLogIndexEntries,
|
|
23
|
-
upsertSqliteLogIndexEntry,
|
|
24
|
-
} from "./sqliteLogIndex";
|
|
25
|
-
import {
|
|
26
|
-
ensurePrivateDirectorySync,
|
|
27
|
-
PRIVATE_FILE_MODE,
|
|
28
|
-
securePrivateFileSync,
|
|
29
|
-
} from "./privateDataPath";
|
|
30
|
-
import { recoverCapturedLogStorage } from "./jsonlRecovery";
|
|
31
|
-
import { getSessionArchiveMaxLogId } from "./sessionArchive";
|
|
32
|
-
|
|
33
|
-
export type LogIndexSummary = {
|
|
34
|
-
id: number;
|
|
35
|
-
timestamp: string;
|
|
36
|
-
method: string;
|
|
37
|
-
path: string;
|
|
38
|
-
model: string | null;
|
|
39
|
-
sessionId: string | null;
|
|
40
|
-
responseStatus: number | null;
|
|
41
|
-
inputTokens: number | null;
|
|
42
|
-
outputTokens: number | null;
|
|
43
|
-
cacheCreationInputTokens: number | null;
|
|
44
|
-
cacheReadInputTokens: number | null;
|
|
45
|
-
elapsedMs: number | null;
|
|
46
|
-
firstChunkMs: number | null;
|
|
47
|
-
totalStreamMs: number | null;
|
|
48
|
-
tokensPerSecond: number | null;
|
|
49
|
-
streaming: boolean;
|
|
50
|
-
userAgent: string | null;
|
|
51
|
-
origin: string | null;
|
|
52
|
-
apiFormat: ApiFormat;
|
|
53
|
-
isTest: boolean;
|
|
54
|
-
replayOfLogId: number | null;
|
|
55
|
-
providerName: string | null;
|
|
56
|
-
clientPort: number | null;
|
|
57
|
-
clientPid: number | null;
|
|
58
|
-
clientCwd: string | null;
|
|
59
|
-
clientProjectFolder: string | null;
|
|
60
|
-
streamingChunksPath: string | null;
|
|
61
|
-
rawRequestBodyBytes: number | null;
|
|
62
|
-
responseTextBytes: number | null;
|
|
63
|
-
captureIncomplete: boolean;
|
|
64
|
-
droppedChunks: number;
|
|
65
|
-
droppedBytes: number;
|
|
66
|
-
captureIncompleteReason: CaptureIncompleteReason | null;
|
|
67
|
-
warnings?: string[];
|
|
68
|
-
stopReason: StopReason | null;
|
|
69
|
-
toolTraceEvents?: ToolTraceEvent[];
|
|
70
|
-
error: string | null;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export type LogIndexEntry = {
|
|
74
|
-
id: number;
|
|
75
|
-
file: string;
|
|
76
|
-
byteOffset: number;
|
|
77
|
-
byteLength: number;
|
|
78
|
-
sessionId?: string | null;
|
|
79
|
-
model?: string | null;
|
|
80
|
-
summary?: LogIndexSummary;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
export type LogIndexEntryMetadata = {
|
|
84
|
-
sessionId: string | null;
|
|
85
|
-
model: string | null;
|
|
86
|
-
summary: LogIndexSummary;
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
type LogIndex = {
|
|
90
|
-
version: number;
|
|
91
|
-
entries: Record<number, LogIndexEntry>;
|
|
92
|
-
maxId: number;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
const INDEX_VERSION = 1;
|
|
96
|
-
const INDEX_FILE = "logs.idx";
|
|
97
|
-
const INDEX_READ_CHUNK_BYTES = 64 * 1024;
|
|
98
|
-
|
|
99
|
-
export { resolveLogDir as getLogDir };
|
|
100
|
-
|
|
101
|
-
function getIndexPath(): string {
|
|
102
|
-
return join(resolveLogDir(), INDEX_FILE);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let cachedIndex: LogIndex | null = null;
|
|
106
|
-
let allocatedMaxId = 0;
|
|
107
|
-
let sqliteBackfillScheduledMaxId = 0;
|
|
108
|
-
|
|
109
|
-
export function createLogIndexEntryMetadata(log: CapturedLog): LogIndexEntryMetadata {
|
|
110
|
-
return {
|
|
111
|
-
sessionId: log.sessionId,
|
|
112
|
-
model: log.model,
|
|
113
|
-
summary: {
|
|
114
|
-
id: log.id,
|
|
115
|
-
timestamp: log.timestamp,
|
|
116
|
-
method: log.method,
|
|
117
|
-
path: log.path,
|
|
118
|
-
model: log.model,
|
|
119
|
-
sessionId: log.sessionId,
|
|
120
|
-
responseStatus: log.responseStatus,
|
|
121
|
-
inputTokens: log.inputTokens,
|
|
122
|
-
outputTokens: log.outputTokens,
|
|
123
|
-
cacheCreationInputTokens: log.cacheCreationInputTokens,
|
|
124
|
-
cacheReadInputTokens: log.cacheReadInputTokens,
|
|
125
|
-
elapsedMs: log.elapsedMs,
|
|
126
|
-
firstChunkMs: log.firstChunkMs ?? null,
|
|
127
|
-
totalStreamMs: log.totalStreamMs ?? null,
|
|
128
|
-
tokensPerSecond: log.tokensPerSecond ?? null,
|
|
129
|
-
streaming: log.streaming,
|
|
130
|
-
userAgent: log.userAgent,
|
|
131
|
-
origin: log.origin,
|
|
132
|
-
apiFormat: log.apiFormat,
|
|
133
|
-
isTest: log.isTest ?? false,
|
|
134
|
-
replayOfLogId: log.replayOfLogId ?? null,
|
|
135
|
-
providerName: log.providerName ?? null,
|
|
136
|
-
clientPort: log.clientPort ?? null,
|
|
137
|
-
clientPid: log.clientPid ?? null,
|
|
138
|
-
clientCwd: log.clientCwd ?? null,
|
|
139
|
-
clientProjectFolder: log.clientProjectFolder ?? null,
|
|
140
|
-
streamingChunksPath: log.streamingChunksPath ?? null,
|
|
141
|
-
rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
|
|
142
|
-
responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
|
|
143
|
-
captureIncomplete: log.captureIncomplete ?? false,
|
|
144
|
-
droppedChunks: log.droppedChunks ?? 0,
|
|
145
|
-
droppedBytes: log.droppedBytes ?? 0,
|
|
146
|
-
captureIncompleteReason: log.captureIncompleteReason ?? null,
|
|
147
|
-
warnings: log.warnings,
|
|
148
|
-
stopReason: log.stopReason ?? extractStopReason(log),
|
|
149
|
-
toolTraceEvents: extractToolTraceEvents(log),
|
|
150
|
-
error: log.error ?? null,
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function createEmptyIndex(): LogIndex {
|
|
156
|
-
return {
|
|
157
|
-
version: INDEX_VERSION,
|
|
158
|
-
entries: {},
|
|
159
|
-
maxId: 0,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function createIndexFromEntries(entries: readonly LogIndexEntry[]): LogIndex {
|
|
164
|
-
const index = createEmptyIndex();
|
|
165
|
-
for (const entry of entries) {
|
|
166
|
-
index.entries[entry.id] = entry;
|
|
167
|
-
index.maxId = Math.max(index.maxId, entry.id);
|
|
168
|
-
}
|
|
169
|
-
return index;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function sqliteProjectionReferencesExistingLedgers(entries: readonly LogIndexEntry[]): boolean {
|
|
173
|
-
const logDir = resolveLogDir();
|
|
174
|
-
for (const entry of entries) {
|
|
175
|
-
if (!entry.file.endsWith(".jsonl")) return false;
|
|
176
|
-
if (!existsSync(join(logDir, entry.file))) return false;
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export async function loadIndex(): Promise<LogIndex> {
|
|
182
|
-
if (cachedIndex !== null) return cachedIndex;
|
|
183
|
-
const sqliteEntries = await listSqliteLogCursorEntries({});
|
|
184
|
-
if (sqliteEntries !== null) {
|
|
185
|
-
if (sqliteProjectionReferencesExistingLedgers(sqliteEntries)) {
|
|
186
|
-
cachedIndex = createIndexFromEntries(sqliteEntries);
|
|
187
|
-
allocatedMaxId = Math.max(allocatedMaxId, cachedIndex.maxId);
|
|
188
|
-
return cachedIndex;
|
|
189
|
-
}
|
|
190
|
-
logger.warn("[logIndex] SQLite projection references missing ledgers; rebuilding from JSONL");
|
|
191
|
-
}
|
|
192
|
-
// logs.idx remains a legacy fallback projection only. If SQLite is unavailable, rebuild from
|
|
193
|
-
// authoritative JSONL rather than allowing a stale, conflicting, or higher derived row to win.
|
|
194
|
-
return await rebuildIndex();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export async function saveIndex(index: LogIndex): Promise<void> {
|
|
198
|
-
const indexPath = getIndexPath();
|
|
199
|
-
const dir = dirname(indexPath);
|
|
200
|
-
|
|
201
|
-
if (!ensurePrivateDirectorySync(dir)) {
|
|
202
|
-
logger.error("[logIndex] Failed to secure index directory:", dir);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const temporaryPath = `${indexPath}.${String(process.pid)}.${randomUUID()}.tmp`;
|
|
207
|
-
try {
|
|
208
|
-
await writeFile(temporaryPath, JSON.stringify(index), {
|
|
209
|
-
encoding: "utf-8",
|
|
210
|
-
mode: PRIVATE_FILE_MODE,
|
|
211
|
-
});
|
|
212
|
-
if (!securePrivateFileSync(temporaryPath)) {
|
|
213
|
-
await unlink(temporaryPath).catch(() => undefined);
|
|
214
|
-
logger.error("[logIndex] Failed to secure temporary index file:", temporaryPath);
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
await rename(temporaryPath, indexPath);
|
|
218
|
-
if (!securePrivateFileSync(indexPath)) {
|
|
219
|
-
logger.error("[logIndex] Failed to secure index file:", indexPath);
|
|
220
|
-
}
|
|
221
|
-
} catch (err) {
|
|
222
|
-
await unlink(temporaryPath).catch(() => undefined);
|
|
223
|
-
logger.error("[logIndex] Failed to save index:", String(err));
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export async function addToIndex(
|
|
228
|
-
id: number,
|
|
229
|
-
file: string,
|
|
230
|
-
byteOffset: number,
|
|
231
|
-
byteLength: number,
|
|
232
|
-
metadata?: LogIndexEntryMetadata,
|
|
233
|
-
): Promise<void> {
|
|
234
|
-
const index = await loadIndex();
|
|
235
|
-
const entry: LogIndexEntry =
|
|
236
|
-
metadata === undefined
|
|
237
|
-
? { id, file, byteOffset, byteLength }
|
|
238
|
-
: { id, file, byteOffset, byteLength, ...metadata };
|
|
239
|
-
index.entries[id] = entry;
|
|
240
|
-
if (id > index.maxId) {
|
|
241
|
-
index.maxId = id;
|
|
242
|
-
}
|
|
243
|
-
const sqliteUpdated = await upsertSqliteLogIndexEntry(entry);
|
|
244
|
-
if (!sqliteUpdated) {
|
|
245
|
-
// Legacy fallback only: defer disk writes to reduce I/O when SQLite is unavailable.
|
|
246
|
-
scheduleIndexFlush();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Refresh the searchable summary after a request reaches its terminal state. The initial index
|
|
252
|
-
* entry is created before the upstream call starts, so leaving it untouched would make compact
|
|
253
|
-
* reads (and the SQLite sidecar) report the pre-request placeholder after finalization.
|
|
254
|
-
*/
|
|
255
|
-
export async function updateLogIndexEntryMetadata(log: CapturedLog): Promise<void> {
|
|
256
|
-
const index = await loadIndex();
|
|
257
|
-
const existing = index.entries[log.id];
|
|
258
|
-
if (existing === undefined) return;
|
|
259
|
-
|
|
260
|
-
const updated: LogIndexEntry = {
|
|
261
|
-
...existing,
|
|
262
|
-
...createLogIndexEntryMetadata(log),
|
|
263
|
-
};
|
|
264
|
-
index.entries[log.id] = updated;
|
|
265
|
-
const sqliteUpdated = await upsertSqliteLogIndexEntry(updated);
|
|
266
|
-
if (!sqliteUpdated) scheduleIndexFlush();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Batch writes: collect pending flushes and write once
|
|
270
|
-
let indexFlushScheduled = false;
|
|
271
|
-
let indexFlushTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
272
|
-
|
|
273
|
-
async function flushIndexAsync(): Promise<void> {
|
|
274
|
-
indexFlushScheduled = false;
|
|
275
|
-
indexFlushTimeout = null;
|
|
276
|
-
if ((await listSqliteLogCursorEntries({})) !== null) return;
|
|
277
|
-
const index = await loadIndex();
|
|
278
|
-
await saveIndex(index);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function scheduleIndexFlush(): void {
|
|
282
|
-
if (indexFlushScheduled) return;
|
|
283
|
-
indexFlushScheduled = true;
|
|
284
|
-
indexFlushTimeout = setTimeout(() => {
|
|
285
|
-
void flushIndexAsync();
|
|
286
|
-
}, 1000); // Flush after 1 second of inactivity
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
export async function flushIndex(): Promise<void> {
|
|
290
|
-
if (indexFlushTimeout !== null) {
|
|
291
|
-
clearTimeout(indexFlushTimeout);
|
|
292
|
-
indexFlushTimeout = null;
|
|
293
|
-
}
|
|
294
|
-
indexFlushScheduled = false;
|
|
295
|
-
if ((await listSqliteLogCursorEntries({})) !== null) return;
|
|
296
|
-
const index = await loadIndex();
|
|
297
|
-
await saveIndex(index);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
export async function findInIndex(id: number): Promise<LogIndexEntry | null> {
|
|
301
|
-
const index = await loadIndex();
|
|
302
|
-
return index.entries[id] ?? null;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export async function listIndexEntries(): Promise<LogIndexEntry[]> {
|
|
306
|
-
const index = await loadIndex();
|
|
307
|
-
const sqliteMaxId = await getSqliteLogIndexMaxId();
|
|
308
|
-
maybeBackfillSqliteIndex(index, sqliteMaxId);
|
|
309
|
-
return Object.values(index.entries).sort((left, right) => left.id - right.id);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export async function listFilteredIndexEntries({
|
|
313
|
-
sessionId,
|
|
314
|
-
model,
|
|
315
|
-
}: {
|
|
316
|
-
sessionId?: string;
|
|
317
|
-
model?: string;
|
|
318
|
-
}): Promise<LogIndexEntry[]> {
|
|
319
|
-
const index = await loadIndex();
|
|
320
|
-
const sqliteMaxId = await getSqliteLogIndexMaxId();
|
|
321
|
-
maybeBackfillSqliteIndex(index, sqliteMaxId);
|
|
322
|
-
return Object.values(index.entries)
|
|
323
|
-
.sort((left, right) => left.id - right.id)
|
|
324
|
-
.filter((entry) => {
|
|
325
|
-
if (sessionId !== undefined && entry.sessionId !== sessionId) return false;
|
|
326
|
-
if (model !== undefined && entry.model !== model) return false;
|
|
327
|
-
return true;
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function maybeBackfillSqliteIndex(index: LogIndex, sqliteMaxId: number | null): void {
|
|
332
|
-
if (sqliteMaxId === null) return;
|
|
333
|
-
if (index.maxId <= sqliteMaxId) return;
|
|
334
|
-
if (index.maxId <= sqliteBackfillScheduledMaxId) return;
|
|
335
|
-
|
|
336
|
-
sqliteBackfillScheduledMaxId = index.maxId;
|
|
337
|
-
const entries = Object.values(index.entries);
|
|
338
|
-
void syncSqliteLogIndexEntries(entries).then((ok) => {
|
|
339
|
-
if (!ok && sqliteMaxId < sqliteBackfillScheduledMaxId) {
|
|
340
|
-
sqliteBackfillScheduledMaxId = sqliteMaxId;
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
type FileIndexResult = {
|
|
346
|
-
entries: Record<number, LogIndexEntry>;
|
|
347
|
-
maxId: number;
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
type ParsedIndexRecord =
|
|
351
|
-
| { status: "blank" }
|
|
352
|
-
| { status: "log"; log: CapturedLog }
|
|
353
|
-
| { status: "invalid"; message: string };
|
|
354
|
-
|
|
355
|
-
function readStringOrNullField(entry: object, field: string): string | null {
|
|
356
|
-
const desc = Object.getOwnPropertyDescriptor(entry, field);
|
|
357
|
-
if (desc === undefined) return null;
|
|
358
|
-
if (typeof desc.value === "string") return desc.value;
|
|
359
|
-
return null;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function readStringField(entry: object, field: string): string {
|
|
363
|
-
return readStringOrNullField(entry, field) ?? "";
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function readNumberOrNullField(entry: object, field: string): number | null {
|
|
367
|
-
const desc = Object.getOwnPropertyDescriptor(entry, field);
|
|
368
|
-
if (desc === undefined) return null;
|
|
369
|
-
return typeof desc.value === "number" ? desc.value : null;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function readBooleanField(entry: object, field: string): boolean {
|
|
373
|
-
const desc = Object.getOwnPropertyDescriptor(entry, field);
|
|
374
|
-
if (desc === undefined) return false;
|
|
375
|
-
return typeof desc.value === "boolean" ? desc.value : false;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function readApiFormatField(entry: object): ApiFormat {
|
|
379
|
-
const value = readStringOrNullField(entry, "apiFormat");
|
|
380
|
-
switch (value) {
|
|
381
|
-
case "anthropic":
|
|
382
|
-
return "anthropic";
|
|
383
|
-
case "openai":
|
|
384
|
-
return "openai";
|
|
385
|
-
case "unknown":
|
|
386
|
-
case null:
|
|
387
|
-
return "unknown";
|
|
388
|
-
default:
|
|
389
|
-
return "unknown";
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function readCaptureIncompleteReasonField(entry: object): CaptureIncompleteReason | null {
|
|
394
|
-
const value = readStringOrNullField(entry, "captureIncompleteReason");
|
|
395
|
-
switch (value) {
|
|
396
|
-
case "capacity-exceeded":
|
|
397
|
-
case "pipeline-closed":
|
|
398
|
-
case "write-failed":
|
|
399
|
-
case "finalize-failed":
|
|
400
|
-
case "upstream-stream-error":
|
|
401
|
-
case "shutdown":
|
|
402
|
-
return value;
|
|
403
|
-
case null:
|
|
404
|
-
default:
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function readStopReasonField(entry: object): StopReason | null {
|
|
410
|
-
const value = readStringOrNullField(entry, "stopReason");
|
|
411
|
-
switch (value) {
|
|
412
|
-
case "end_turn":
|
|
413
|
-
case "tool_use":
|
|
414
|
-
case "stop":
|
|
415
|
-
case "length":
|
|
416
|
-
return value;
|
|
417
|
-
case null:
|
|
418
|
-
default:
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function readStringArrayField(entry: object, field: string): string[] | undefined {
|
|
424
|
-
const desc = Object.getOwnPropertyDescriptor(entry, field);
|
|
425
|
-
if (desc === undefined || !Array.isArray(desc.value)) return undefined;
|
|
426
|
-
|
|
427
|
-
const values: string[] = [];
|
|
428
|
-
for (const item of desc.value) {
|
|
429
|
-
if (typeof item !== "string") return undefined;
|
|
430
|
-
values.push(item);
|
|
431
|
-
}
|
|
432
|
-
return values;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function readTextByteLength(entry: object, bytesField: string, textField: string): number | null {
|
|
436
|
-
const explicitBytes = readNumberOrNullField(entry, bytesField);
|
|
437
|
-
if (explicitBytes !== null && explicitBytes >= 0) return explicitBytes;
|
|
438
|
-
return textByteLength(readStringOrNullField(entry, textField));
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function readLogIndexSummary(entry: object, id: number): LogIndexSummary {
|
|
442
|
-
return {
|
|
443
|
-
id,
|
|
444
|
-
timestamp: readStringField(entry, "timestamp"),
|
|
445
|
-
method: readStringField(entry, "method"),
|
|
446
|
-
path: readStringField(entry, "path"),
|
|
447
|
-
model: readStringOrNullField(entry, "model"),
|
|
448
|
-
sessionId: readStringOrNullField(entry, "sessionId"),
|
|
449
|
-
responseStatus: readNumberOrNullField(entry, "responseStatus"),
|
|
450
|
-
inputTokens: readNumberOrNullField(entry, "inputTokens"),
|
|
451
|
-
outputTokens: readNumberOrNullField(entry, "outputTokens"),
|
|
452
|
-
cacheCreationInputTokens: readNumberOrNullField(entry, "cacheCreationInputTokens"),
|
|
453
|
-
cacheReadInputTokens: readNumberOrNullField(entry, "cacheReadInputTokens"),
|
|
454
|
-
elapsedMs: readNumberOrNullField(entry, "elapsedMs"),
|
|
455
|
-
firstChunkMs: readNumberOrNullField(entry, "firstChunkMs"),
|
|
456
|
-
totalStreamMs: readNumberOrNullField(entry, "totalStreamMs"),
|
|
457
|
-
tokensPerSecond: readNumberOrNullField(entry, "tokensPerSecond"),
|
|
458
|
-
streaming: readBooleanField(entry, "streaming"),
|
|
459
|
-
userAgent: readStringOrNullField(entry, "userAgent"),
|
|
460
|
-
origin: readStringOrNullField(entry, "origin"),
|
|
461
|
-
apiFormat: readApiFormatField(entry),
|
|
462
|
-
isTest: readBooleanField(entry, "isTest"),
|
|
463
|
-
replayOfLogId: readNumberOrNullField(entry, "replayOfLogId"),
|
|
464
|
-
providerName: readStringOrNullField(entry, "providerName"),
|
|
465
|
-
clientPort: readNumberOrNullField(entry, "clientPort"),
|
|
466
|
-
clientPid: readNumberOrNullField(entry, "clientPid"),
|
|
467
|
-
clientCwd: readStringOrNullField(entry, "clientCwd"),
|
|
468
|
-
clientProjectFolder: readStringOrNullField(entry, "clientProjectFolder"),
|
|
469
|
-
streamingChunksPath: readStringOrNullField(entry, "streamingChunksPath"),
|
|
470
|
-
rawRequestBodyBytes: readTextByteLength(entry, "rawRequestBodyBytes", "rawRequestBody"),
|
|
471
|
-
responseTextBytes: readTextByteLength(entry, "responseTextBytes", "responseText"),
|
|
472
|
-
captureIncomplete: readBooleanField(entry, "captureIncomplete"),
|
|
473
|
-
droppedChunks: readNumberOrNullField(entry, "droppedChunks") ?? 0,
|
|
474
|
-
droppedBytes: readNumberOrNullField(entry, "droppedBytes") ?? 0,
|
|
475
|
-
captureIncompleteReason: readCaptureIncompleteReasonField(entry),
|
|
476
|
-
warnings: readStringArrayField(entry, "warnings"),
|
|
477
|
-
stopReason: readStopReasonField(entry),
|
|
478
|
-
error: readStringOrNullField(entry, "error"),
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function parseIndexRecord(record: Buffer, file: string, byteOffset: number): ParsedIndexRecord {
|
|
483
|
-
const content =
|
|
484
|
-
record.length > 0 && record[record.length - 1] === 0x0d ? record.subarray(0, -1) : record;
|
|
485
|
-
const line = content.toString("utf-8");
|
|
486
|
-
if (line.trim() === "") return { status: "blank" };
|
|
487
|
-
|
|
488
|
-
let parsed: unknown;
|
|
489
|
-
try {
|
|
490
|
-
parsed = JSON.parse(line);
|
|
491
|
-
} catch (error) {
|
|
492
|
-
return {
|
|
493
|
-
status: "invalid",
|
|
494
|
-
message: `Malformed JSONL record in ${file} at byte ${String(byteOffset)}: ${String(error)}`,
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
const validated = CapturedLogSchema.safeParse(parsed);
|
|
498
|
-
return validated.success
|
|
499
|
-
? { status: "log", log: validated.data }
|
|
500
|
-
: {
|
|
501
|
-
status: "invalid",
|
|
502
|
-
message: `Invalid captured-log record in ${file} at byte ${String(byteOffset)}`,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function addIndexedRecord(
|
|
507
|
-
result: FileIndexResult,
|
|
508
|
-
parsed: ParsedIndexRecord,
|
|
509
|
-
file: string,
|
|
510
|
-
byteOffset: number,
|
|
511
|
-
byteLength: number,
|
|
512
|
-
): Error | null {
|
|
513
|
-
switch (parsed.status) {
|
|
514
|
-
case "blank":
|
|
515
|
-
return null;
|
|
516
|
-
case "invalid":
|
|
517
|
-
return new Error(parsed.message);
|
|
518
|
-
case "log": {
|
|
519
|
-
const entry = parsed.log;
|
|
520
|
-
result.maxId = Math.max(result.maxId, entry.id);
|
|
521
|
-
result.entries[entry.id] = {
|
|
522
|
-
id: entry.id,
|
|
523
|
-
file,
|
|
524
|
-
byteOffset,
|
|
525
|
-
byteLength,
|
|
526
|
-
sessionId: entry.sessionId,
|
|
527
|
-
model: entry.model,
|
|
528
|
-
summary: readLogIndexSummary(entry, entry.id),
|
|
529
|
-
};
|
|
530
|
-
return null;
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
async function indexFile(filePath: string, file: string): Promise<FileIndexResult> {
|
|
536
|
-
const result: FileIndexResult = { entries: {}, maxId: 0 };
|
|
537
|
-
let handle: Awaited<ReturnType<typeof open>> | null = null;
|
|
538
|
-
try {
|
|
539
|
-
handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
540
|
-
const fileStats = await handle.stat();
|
|
541
|
-
if (!fileStats.isFile()) {
|
|
542
|
-
return await Promise.reject(new Error(`Captured-log ledger is not a regular file: ${file}`));
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const readBuffer = Buffer.alloc(INDEX_READ_CHUNK_BYTES);
|
|
546
|
-
let position = 0;
|
|
547
|
-
let pending = Buffer.alloc(0);
|
|
548
|
-
let pendingOffset = 0;
|
|
549
|
-
while (position < fileStats.size) {
|
|
550
|
-
const length = Math.min(INDEX_READ_CHUNK_BYTES, fileStats.size - position);
|
|
551
|
-
const read = await handle.read(readBuffer, 0, length, position);
|
|
552
|
-
if (read.bytesRead === 0) {
|
|
553
|
-
return await Promise.reject(
|
|
554
|
-
new Error(`Captured-log ledger changed while rebuilding index: ${file}`),
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
const chunk = readBuffer.subarray(0, read.bytesRead);
|
|
558
|
-
const combined = pending.length === 0 ? Buffer.from(chunk) : Buffer.concat([pending, chunk]);
|
|
559
|
-
let consumed = 0;
|
|
560
|
-
let newline = combined.indexOf(0x0a, consumed);
|
|
561
|
-
while (newline !== -1) {
|
|
562
|
-
const record = combined.subarray(consumed, newline);
|
|
563
|
-
const byteOffset = pendingOffset + consumed;
|
|
564
|
-
const byteLength = record.length + 1;
|
|
565
|
-
const error = addIndexedRecord(
|
|
566
|
-
result,
|
|
567
|
-
parseIndexRecord(record, file, byteOffset),
|
|
568
|
-
file,
|
|
569
|
-
byteOffset,
|
|
570
|
-
byteLength,
|
|
571
|
-
);
|
|
572
|
-
if (error !== null) return await Promise.reject(error);
|
|
573
|
-
consumed = newline + 1;
|
|
574
|
-
newline = combined.indexOf(0x0a, consumed);
|
|
575
|
-
}
|
|
576
|
-
pending = Buffer.from(combined.subarray(consumed));
|
|
577
|
-
pendingOffset += consumed;
|
|
578
|
-
position += read.bytesRead;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
if (pending.length > 0) {
|
|
582
|
-
return await Promise.reject(
|
|
583
|
-
new Error(`Captured-log ledger ended without recovery delimiter: ${file}`),
|
|
584
|
-
);
|
|
585
|
-
}
|
|
586
|
-
return result;
|
|
587
|
-
} finally {
|
|
588
|
-
await handle?.close().catch(() => undefined);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
export async function rebuildIndex(): Promise<LogIndex> {
|
|
593
|
-
const logDir = resolveLogDir();
|
|
594
|
-
const newIndex = createEmptyIndex();
|
|
595
|
-
const recovery = await recoverCapturedLogStorage(logDir);
|
|
596
|
-
if (recovery.level === "unavailable") {
|
|
597
|
-
return await Promise.reject(
|
|
598
|
-
new Error(recovery.diagnostic ?? "Captured-log recovery blocked index rebuild"),
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (!existsSync(logDir)) {
|
|
603
|
-
cachedIndex = newIndex;
|
|
604
|
-
allocatedMaxId = 0;
|
|
605
|
-
await saveIndex(newIndex);
|
|
606
|
-
await replaceSqliteLogIndexEntries([]);
|
|
607
|
-
return newIndex;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
const files = (await readdir(logDir)).filter((f) => f.endsWith(".jsonl")).sort();
|
|
611
|
-
const CONCURRENCY = 4;
|
|
612
|
-
|
|
613
|
-
// Process files in bounded-concurrency batches
|
|
614
|
-
for (let i = 0; i < files.length; i += CONCURRENCY) {
|
|
615
|
-
const batch = files.slice(i, i + CONCURRENCY);
|
|
616
|
-
const results = await Promise.all(batch.map((file) => indexFile(join(logDir, file), file)));
|
|
617
|
-
|
|
618
|
-
for (const result of results) {
|
|
619
|
-
Object.assign(newIndex.entries, result.entries);
|
|
620
|
-
newIndex.maxId = Math.max(newIndex.maxId, result.maxId);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
cachedIndex = newIndex;
|
|
625
|
-
allocatedMaxId = Math.max(allocatedMaxId, newIndex.maxId);
|
|
626
|
-
await saveIndex(newIndex);
|
|
627
|
-
await replaceSqliteLogIndexEntries(Object.values(newIndex.entries));
|
|
628
|
-
return newIndex;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Promise-chaining queue for atomic ID generation. Each caller chains on the
|
|
632
|
-
// previous promise, guaranteeing mutual exclusion without the broken
|
|
633
|
-
// releaseLock pattern that allowed concurrent entry when 3+ callers waited.
|
|
634
|
-
let idGenerationChain: Promise<void> = Promise.resolve();
|
|
635
|
-
|
|
636
|
-
async function withIdLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
637
|
-
const run = idGenerationChain.then(fn);
|
|
638
|
-
// Keep the chain alive regardless of success/failure so the next caller
|
|
639
|
-
// never sees a rejected antecedent.
|
|
640
|
-
idGenerationChain = run.then(
|
|
641
|
-
() => {},
|
|
642
|
-
() => {},
|
|
643
|
-
);
|
|
644
|
-
return run;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
export async function getNextLogId(): Promise<number> {
|
|
648
|
-
return withIdLock(async () => {
|
|
649
|
-
const index = await loadIndex();
|
|
650
|
-
const archiveMaxId = await getSessionArchiveMaxLogId();
|
|
651
|
-
const nextId = Math.max(index.maxId, archiveMaxId, allocatedMaxId) + 1;
|
|
652
|
-
allocatedMaxId = nextId;
|
|
653
|
-
return nextId;
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
export function getCurrentLogFile(): string {
|
|
658
|
-
const now = new Date();
|
|
659
|
-
const yyyy = now.getUTCFullYear();
|
|
660
|
-
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
661
|
-
const dd = String(now.getUTCDate()).padStart(2, "0");
|
|
662
|
-
return `${yyyy}-${mm}-${dd}.jsonl`;
|
|
663
|
-
}
|