@tonyclaw/agent-inspector 4.0.1 → 4.0.3
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 +193 -110
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-CTHqmroT.mjs → router-ChILzhmz.mjs} +92 -4
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-L3n4J-Or.js → CompareDrawer-Co3cUF_I.js} +1 -1
- package/.output/ui/assets/{InspectorPet-D5o_3OZS.js → InspectorPet-jFcrL37X.js} +1 -1
- package/.output/ui/assets/{ProxyViewerContainer-Ocagx75S.js → ProxyViewerContainer-BPzt_VpM.js} +4 -4
- package/.output/ui/assets/{ReplayDialog-Bk7x1h-y.js → ReplayDialog-C6O_zuwI.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-U4lzL7Sc.js → RequestAnatomy-Ca6NvB7L.js} +1 -1
- package/.output/ui/assets/{ResponseView-LOWfvBLK.js → ResponseView-BzqKcveJ.js} +1 -1
- package/.output/ui/assets/{StreamingChunkSequence-DPfsrrXQ.js → StreamingChunkSequence-9pCZIsS_.js} +1 -1
- package/.output/ui/assets/{_sessionId-0MlWS-CO.js → _sessionId-BMP3KVGR.js} +1 -1
- package/.output/ui/assets/{_sessionId-OdX2Jvt1.js → _sessionId-y4eRoT1H.js} +1 -1
- package/.output/ui/assets/{index-Ea-c8hPg.js → index-CH5w-haG.js} +1 -1
- package/.output/ui/assets/{index-zVVS53Hd.js → index-CmocEhyU.js} +1 -1
- package/.output/ui/assets/{index-Du4qNOOt.js → index-DT8sMJ_n.js} +2 -2
- package/.output/ui/assets/{index-gvFj_9K6.js → index-bfsbIRZa.js} +1 -1
- package/.output/ui/assets/{json-viewer-C1WAMzg3.js → json-viewer-BfFOSDG5.js} +1 -1
- package/.output/ui/assets/{jszip.min-B8AKu2zm.js → jszip.min-BkbVkOsp.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
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
writeFileSync,
|
|
3
|
-
readFileSync,
|
|
4
|
-
unlinkSync,
|
|
5
|
-
existsSync,
|
|
6
|
-
renameSync,
|
|
7
|
-
copyFileSync,
|
|
8
|
-
} from "node:fs";
|
|
9
|
-
import { join, isAbsolute } from "node:path";
|
|
10
|
-
import { logger } from "./logger";
|
|
11
|
-
import { z } from "zod";
|
|
12
|
-
import { JsonValueSchema, type StreamingChunk } from "./schemas";
|
|
13
|
-
import { getDataDir } from "./dataDir";
|
|
14
|
-
import {
|
|
15
|
-
ensurePrivateDirectorySync,
|
|
16
|
-
PRIVATE_FILE_MODE,
|
|
17
|
-
securePrivateFileSync,
|
|
18
|
-
} from "./privateDataPath";
|
|
19
|
-
|
|
20
|
-
export type StreamingChunksData = {
|
|
21
|
-
chunks: StreamingChunk[];
|
|
22
|
-
truncated?: boolean;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const StreamingChunkSchema: z.ZodType<StreamingChunk> = z.object({
|
|
26
|
-
index: z.number(),
|
|
27
|
-
timestamp: z.number(),
|
|
28
|
-
type: z.string(),
|
|
29
|
-
data: JsonValueSchema,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const StreamingChunksDataSchema = z.object({
|
|
33
|
-
chunks: z.array(StreamingChunkSchema),
|
|
34
|
-
truncated: z.boolean().optional(),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
export function getChunksDir(): string {
|
|
38
|
-
const chunksDirEnv = process.env["CHUNKS_DIR"];
|
|
39
|
-
if (chunksDirEnv !== undefined && chunksDirEnv !== "") {
|
|
40
|
-
return isAbsolute(chunksDirEnv) ? chunksDirEnv : join(getDataDir(), chunksDirEnv);
|
|
41
|
-
}
|
|
42
|
-
return join(getDataDir(), "chunks");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getChunkFilePath(logId: number): string {
|
|
46
|
-
return join(getChunksDir(), `${logId}.json`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function getTempFilePath(logId: number): string {
|
|
50
|
-
return join(getChunksDir(), `.${logId}.tmp`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Write chunks to disk atomically: write to temp file first, then rename.
|
|
55
|
-
* Returns the absolute path to the written chunk file.
|
|
56
|
-
*/
|
|
57
|
-
export function writeChunks(logId: number, chunks: StreamingChunk[], truncated?: boolean): string {
|
|
58
|
-
const dir = getChunksDir();
|
|
59
|
-
const targetPath = getChunkFilePath(logId);
|
|
60
|
-
const tempPath = getTempFilePath(logId);
|
|
61
|
-
|
|
62
|
-
// Ensure directory exists
|
|
63
|
-
if (!ensurePrivateDirectorySync(dir)) {
|
|
64
|
-
logger.error("[chunkStorage] Failed to secure chunks directory:", dir);
|
|
65
|
-
return targetPath;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const data: StreamingChunksData = { chunks, truncated };
|
|
69
|
-
|
|
70
|
-
// Write to temp file first
|
|
71
|
-
try {
|
|
72
|
-
writeFileSync(tempPath, JSON.stringify(data), {
|
|
73
|
-
encoding: "utf-8",
|
|
74
|
-
mode: PRIVATE_FILE_MODE,
|
|
75
|
-
});
|
|
76
|
-
if (!securePrivateFileSync(tempPath)) {
|
|
77
|
-
unlinkSync(tempPath);
|
|
78
|
-
logger.error("[chunkStorage] Failed to secure chunks temp file:", tempPath);
|
|
79
|
-
return targetPath;
|
|
80
|
-
}
|
|
81
|
-
} catch (err) {
|
|
82
|
-
logger.error("[chunkStorage] Failed to write chunks temp file:", String(err));
|
|
83
|
-
return targetPath;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Rename to target (atomic on most filesystems)
|
|
87
|
-
try {
|
|
88
|
-
renameSync(tempPath, targetPath);
|
|
89
|
-
} catch (err) {
|
|
90
|
-
logger.warn("[chunkStorage] Rename failed, falling back to copy+delete:", String(err));
|
|
91
|
-
// If rename fails, try copy + delete
|
|
92
|
-
try {
|
|
93
|
-
copyFileSync(tempPath, targetPath);
|
|
94
|
-
if (!securePrivateFileSync(targetPath)) {
|
|
95
|
-
logger.error("[chunkStorage] Failed to secure chunks file:", targetPath);
|
|
96
|
-
}
|
|
97
|
-
unlinkSync(tempPath);
|
|
98
|
-
} catch (copyErr) {
|
|
99
|
-
logger.error("[chunkStorage] Failed to copy chunks file:", String(copyErr));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return targetPath;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Read chunks from a chunk file path.
|
|
108
|
-
* Returns null if the file does not exist or cannot be read.
|
|
109
|
-
*/
|
|
110
|
-
export function readChunks(path: string): StreamingChunksData | null {
|
|
111
|
-
if (!existsSync(path)) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
const content = readFileSync(path, "utf-8");
|
|
116
|
-
const parsed: unknown = JSON.parse(content);
|
|
117
|
-
const result = StreamingChunksDataSchema.safeParse(parsed);
|
|
118
|
-
if (!result.success) return null;
|
|
119
|
-
return result.data;
|
|
120
|
-
} catch {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
}
|
package/src/proxy/config.ts
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { writeFileSync, readFileSync, unlinkSync, existsSync, renameSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { logger } from "./logger";
|
|
4
|
-
import { getDataDir } from "./dataDir";
|
|
5
|
-
import {
|
|
6
|
-
ensurePrivateDirectorySync,
|
|
7
|
-
PRIVATE_FILE_MODE,
|
|
8
|
-
securePrivateFileSync,
|
|
9
|
-
} from "./privateDataPath";
|
|
10
|
-
import {
|
|
11
|
-
CaptureModeSchema,
|
|
12
|
-
DEFAULT_CAPTURE_MODE,
|
|
13
|
-
DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS,
|
|
14
|
-
DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
15
|
-
DEFAULT_TIME_DISPLAY_FORMAT,
|
|
16
|
-
RuntimeConfigSchema,
|
|
17
|
-
type CaptureMode,
|
|
18
|
-
type RuntimeConfig,
|
|
19
|
-
} from "../lib/runtimeConfig";
|
|
20
|
-
|
|
21
|
-
// Re-export so server-side callers (api/config.ts, tests) keep the
|
|
22
|
-
// existing import path `from "../../proxy/config"`.
|
|
23
|
-
export { RuntimeConfigSchema };
|
|
24
|
-
export type { RuntimeConfig };
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Runtime-mutable server config. Includes proxy behavior flags and UI tuning
|
|
28
|
-
* settings that should take effect without restarting the dev server. The API
|
|
29
|
-
* layer reads and writes the same object via `getConfig` / `setConfig`.
|
|
30
|
-
*
|
|
31
|
-
* Boot-time precedence (first match wins):
|
|
32
|
-
* 1. `data/config.json` on disk (persisted state)
|
|
33
|
-
* 2. `AGENT_INSPECTOR_STRIP_CLAUDE_CODE_BILLING_HEADER` env var
|
|
34
|
-
* 3. `false` (off by default, to keep the proxy transparent)
|
|
35
|
-
*
|
|
36
|
-
* The env var is read once at module init; changing it requires a restart.
|
|
37
|
-
* Once the user has saved a value via the API, the persisted file takes
|
|
38
|
-
* precedence over the env var for the lifetime of the process.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
// --- Persistence ---
|
|
42
|
-
|
|
43
|
-
function getConfigFilePath(): string {
|
|
44
|
-
return join(getDataDir(), "config.json");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function getConfigTempPath(): string {
|
|
48
|
-
return join(getDataDir(), "config.json.tmp");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// --- Module state ---
|
|
52
|
-
|
|
53
|
-
// Internal: writeable singleton. Read via getConfig(), mutate via setConfig().
|
|
54
|
-
let currentConfig: RuntimeConfig = resolveInitialConfig();
|
|
55
|
-
|
|
56
|
-
type MutableRuntimeConfig = Omit<RuntimeConfig, "captureMode">;
|
|
57
|
-
|
|
58
|
-
function resolveInitialCaptureMode(): CaptureMode {
|
|
59
|
-
const value = process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
|
|
60
|
-
if (value === undefined || value === "") return DEFAULT_CAPTURE_MODE;
|
|
61
|
-
|
|
62
|
-
const result = CaptureModeSchema.safeParse(value);
|
|
63
|
-
if (result.success) return result.data;
|
|
64
|
-
|
|
65
|
-
logger.warn(`[config] Invalid capture mode ${value}; falling back to ${DEFAULT_CAPTURE_MODE}`);
|
|
66
|
-
return DEFAULT_CAPTURE_MODE;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function resolveInitialConfig(): RuntimeConfig {
|
|
70
|
-
const captureMode = resolveInitialCaptureMode();
|
|
71
|
-
|
|
72
|
-
// 1. Persisted file
|
|
73
|
-
const filePath = getConfigFilePath();
|
|
74
|
-
if (existsSync(filePath)) {
|
|
75
|
-
try {
|
|
76
|
-
const content = readFileSync(filePath, "utf-8");
|
|
77
|
-
const parsed: unknown = JSON.parse(content);
|
|
78
|
-
const result = RuntimeConfigSchema.safeParse(parsed);
|
|
79
|
-
if (result.success) {
|
|
80
|
-
logger.info(`[config] Loaded persisted config from ${filePath}`);
|
|
81
|
-
return { ...result.data, captureMode };
|
|
82
|
-
}
|
|
83
|
-
logger.warn(
|
|
84
|
-
`[config] Persisted config at ${filePath} is invalid; falling back to env/default`,
|
|
85
|
-
);
|
|
86
|
-
} catch (err) {
|
|
87
|
-
logger.warn(
|
|
88
|
-
`[config] Failed to read persisted config at ${filePath}: ${String(err)}; falling back to env/default`,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 2. Env var
|
|
94
|
-
if (process.env["AGENT_INSPECTOR_STRIP_CLAUDE_CODE_BILLING_HEADER"] === "1") {
|
|
95
|
-
return {
|
|
96
|
-
stripClaudeCodeBillingHeader: true,
|
|
97
|
-
hasSeenOnboarding: false,
|
|
98
|
-
slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
99
|
-
providerTestTimeoutSeconds: DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS,
|
|
100
|
-
timeDisplayFormat: DEFAULT_TIME_DISPLAY_FORMAT,
|
|
101
|
-
captureMode,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 3. Default off
|
|
106
|
-
return {
|
|
107
|
-
stripClaudeCodeBillingHeader: false,
|
|
108
|
-
hasSeenOnboarding: false,
|
|
109
|
-
slowResponseThresholdSeconds: DEFAULT_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
110
|
-
providerTestTimeoutSeconds: DEFAULT_PROVIDER_TEST_TIMEOUT_SECONDS,
|
|
111
|
-
timeDisplayFormat: DEFAULT_TIME_DISPLAY_FORMAT,
|
|
112
|
-
captureMode,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Returns the current config. Synchronous; safe to call on the hot path.
|
|
118
|
-
*/
|
|
119
|
-
export function getConfig(): RuntimeConfig {
|
|
120
|
-
return currentConfig;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Applies a partial update after atomically persisting it to disk.
|
|
125
|
-
* Returns the updated full config on success or null when persistence
|
|
126
|
-
* fails. A failed write leaves both the in-memory value and the existing
|
|
127
|
-
* on-disk value unchanged.
|
|
128
|
-
*/
|
|
129
|
-
export function setConfig(patch: Partial<MutableRuntimeConfig>): RuntimeConfig | null {
|
|
130
|
-
const next: RuntimeConfig = {
|
|
131
|
-
...currentConfig,
|
|
132
|
-
...patch,
|
|
133
|
-
captureMode: currentConfig.captureMode,
|
|
134
|
-
};
|
|
135
|
-
if (!persistConfig(next)) return null;
|
|
136
|
-
currentConfig = next;
|
|
137
|
-
return next;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* For tests: reset the in-memory config to a known value without
|
|
142
|
-
* touching disk. Use `setConfig` in production code.
|
|
143
|
-
*/
|
|
144
|
-
export function _resetConfigForTests(value: RuntimeConfig): void {
|
|
145
|
-
currentConfig = value;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* For tests: re-run the boot-time resolution against the current env vars
|
|
150
|
-
* and the on-disk config file, and update the in-memory value. Use this to
|
|
151
|
-
* test env-var precedence without having to reload the module.
|
|
152
|
-
*/
|
|
153
|
-
export function _rebootForTests(): void {
|
|
154
|
-
currentConfig = resolveInitialConfig();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// --- Atomic write ---
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Atomic write: secure temp file + rename in the same directory.
|
|
161
|
-
* Returns true on success, false on failure (logged but not thrown). There is
|
|
162
|
-
* deliberately no copy fallback because replacing the target through a copy
|
|
163
|
-
* could expose a partially written config after a crash.
|
|
164
|
-
*/
|
|
165
|
-
function persistConfig(value: RuntimeConfig): boolean {
|
|
166
|
-
const dir = getDataDir();
|
|
167
|
-
const targetPath = getConfigFilePath();
|
|
168
|
-
const tempPath = getConfigTempPath();
|
|
169
|
-
|
|
170
|
-
if (!ensurePrivateDirectorySync(dir)) {
|
|
171
|
-
logger.error(`[config] Failed to secure data directory ${dir}`);
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
writeFileSync(tempPath, JSON.stringify(toPersistedConfig(value)), {
|
|
177
|
-
encoding: "utf-8",
|
|
178
|
-
mode: PRIVATE_FILE_MODE,
|
|
179
|
-
flush: true,
|
|
180
|
-
});
|
|
181
|
-
if (!securePrivateFileSync(tempPath)) {
|
|
182
|
-
removeConfigTempFile(tempPath);
|
|
183
|
-
logger.error(`[config] Failed to secure config temp file ${tempPath}`);
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
} catch (err) {
|
|
187
|
-
removeConfigTempFile(tempPath);
|
|
188
|
-
logger.error(`[config] Failed to write config temp file: ${String(err)}`);
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
renameSync(tempPath, targetPath);
|
|
194
|
-
return true;
|
|
195
|
-
} catch (err) {
|
|
196
|
-
removeConfigTempFile(tempPath);
|
|
197
|
-
logger.error(`[config] Failed to atomically replace config file: ${String(err)}`);
|
|
198
|
-
return false;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function removeConfigTempFile(tempPath: string): void {
|
|
203
|
-
try {
|
|
204
|
-
unlinkSync(tempPath);
|
|
205
|
-
} catch {
|
|
206
|
-
// Best effort. A stale temp path is never read as runtime configuration.
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function toPersistedConfig(value: RuntimeConfig): Record<string, unknown> {
|
|
211
|
-
return {
|
|
212
|
-
stripClaudeCodeBillingHeader: value.stripClaudeCodeBillingHeader,
|
|
213
|
-
hasSeenOnboarding: value.hasSeenOnboarding,
|
|
214
|
-
slowResponseThresholdSeconds: value.slowResponseThresholdSeconds,
|
|
215
|
-
providerTestTimeoutSeconds: value.providerTestTimeoutSeconds,
|
|
216
|
-
timeDisplayFormat: value.timeDisplayFormat,
|
|
217
|
-
};
|
|
218
|
-
}
|
package/src/proxy/constants.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// Proxy module constants
|
|
2
|
-
|
|
3
|
-
export const DEFAULT_UPSTREAM = "https://api.anthropic.com";
|
|
4
|
-
export const DEFAULT_OPENAI_UPSTREAM = "https://api.openai.com/v1";
|
|
5
|
-
|
|
6
|
-
// Unified proxy identity - used as user-agent and x-proxy-identity header value
|
|
7
|
-
export const PROXY_IDENTITY = process.env["PROXY_IDENTITY"] ?? "inspector9527@Tony";
|
|
8
|
-
|
|
9
|
-
// API paths
|
|
10
|
-
export const PATH_V1_CHAT_COMPLETIONS = "/v1/chat/completions";
|
|
11
|
-
export const PATH_CHAT_COMPLETIONS = "/chat/completions";
|
|
12
|
-
export const PATH_V1_RESPONSES = "/v1/responses";
|
|
13
|
-
export const PATH_RESPONSES = "/responses";
|
|
14
|
-
export const PATH_V1_MESSAGES = "/v1/messages";
|
|
15
|
-
|
|
16
|
-
// Header names
|
|
17
|
-
export const HEADER_CONTENT_TYPE = "content-type";
|
|
18
|
-
export const HEADER_USER_AGENT = "user-agent";
|
|
19
|
-
export const HEADER_X_PROXY_IDENTITY = "x-proxy-identity";
|
|
20
|
-
export const HEADER_AUTHORIZATION = "authorization";
|
|
21
|
-
export const HEADER_X_API_KEY = "x-api-key";
|
|
22
|
-
export const HEADER_CONTENT_ENCODING = "content-encoding";
|
|
23
|
-
export const HEADER_CONTENT_LENGTH = "content-length";
|
|
24
|
-
export const HEADER_HOST = "host";
|
|
25
|
-
|
|
26
|
-
// Auth header types
|
|
27
|
-
export const AUTH_HEADER_X_API_KEY = "x-api-key";
|
|
28
|
-
|
|
29
|
-
// Headers whose values contain secrets (API keys, bearer tokens).
|
|
30
|
-
// These are redacted before being written to logs when captureMode is "full".
|
|
31
|
-
export const SENSITIVE_HEADERS = new Set([
|
|
32
|
-
HEADER_AUTHORIZATION,
|
|
33
|
-
HEADER_X_API_KEY,
|
|
34
|
-
"cookie",
|
|
35
|
-
"set-cookie",
|
|
36
|
-
"proxy-authorization",
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
// Content types
|
|
40
|
-
export const CONTENT_TYPE_EVENT_STREAM = "text/event-stream";
|
|
41
|
-
|
|
42
|
-
// Response status
|
|
43
|
-
export const STATUS_FORBIDDEN = 403;
|
|
44
|
-
export const STATUS_BAD_GATEWAY = 502;
|
|
45
|
-
export const STATUS_PAYLOAD_TOO_LARGE = 413;
|
|
46
|
-
|
|
47
|
-
// Maximum request body size accepted by the proxy (50 MB).
|
|
48
|
-
// Legitimate LLM API requests (system prompts, conversation history, tool
|
|
49
|
-
// definitions) are well under this limit; larger payloads are almost certainly
|
|
50
|
-
// malicious or malformed.
|
|
51
|
-
export const MAX_REQUEST_BODY_BYTES = 50 * 1024 * 1024;
|
|
52
|
-
|
|
53
|
-
// Standard/safe headers to preserve when forwarding to upstream.
|
|
54
|
-
// NOTE: Authorization is intentionally excluded to prevent client auth tokens from leaking.
|
|
55
|
-
export const PRESERVE_HEADERS = new Set([HEADER_CONTENT_TYPE]);
|
|
56
|
-
|
|
57
|
-
// Opt-in rewrite: the *initial* value for the strip-Claude-Code-billing-header
|
|
58
|
-
// flag. The runtime, mutable source of truth is `src/proxy/config.ts`, which
|
|
59
|
-
// reads this constant at module-load time as its precedence-2 fallback (after
|
|
60
|
-
// the persisted `data/config.json`, before the `false` default). After boot,
|
|
61
|
-
// the handler reads `getConfig().stripClaudeCodeBillingHeader` on every
|
|
62
|
-
// request, so a PATCH to `/api/config` (or the Settings → Proxy tab in the UI)
|
|
63
|
-
// takes effect without a restart. The env var itself is read once at boot.
|
|
64
|
-
//
|
|
65
|
-
// What it does: when enabled, the proxy strips the
|
|
66
|
-
// `x-anthropic-billing-header:` text block that Claude Code prepends to the
|
package/src/proxy/dataDir.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { cpSync, existsSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
2
|
-
import { isAbsolute, join } from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
ensurePrivateDirectorySync,
|
|
5
|
-
isPathTreeFreeOfIndirectionSync,
|
|
6
|
-
securePrivateTreeSync,
|
|
7
|
-
} from "./privateDataPath";
|
|
8
|
-
|
|
9
|
-
const CURRENT_DATA_DIR_NAME = ".agent-inspector";
|
|
10
|
-
const LEGACY_DATA_DIR_NAMES = [".llm-inspector", ".llm-agent"];
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Compute the directory where agent-inspector stores its on-disk state
|
|
14
|
-
* (runtime config, providers config, log chunks, etc.). Centralised here
|
|
15
|
-
* so every store agrees on the same path.
|
|
16
|
-
*
|
|
17
|
-
* Resolution order (first match wins):
|
|
18
|
-
* 1. `AGENT_INSPECTOR_DATA_DIR` env var - absolute path used as-is,
|
|
19
|
-
* relative path joined to the OS-specific base below.
|
|
20
|
-
* 2. `AGENT_INSPECTOR_CONFIG_DIR` env var - kept for backward compat
|
|
21
|
-
* with the runtime config; treated as a directory *name* (relative
|
|
22
|
-
* to the OS-specific base) when not absolute.
|
|
23
|
-
* 3. OS-specific base + `.agent-inspector`.
|
|
24
|
-
*
|
|
25
|
-
* Windows base is `%USERPROFILE%` (falling back to `%APPDATA%`, then `C:\`).
|
|
26
|
-
* macOS / Linux base is `$HOME` (falling back to `/tmp`).
|
|
27
|
-
*
|
|
28
|
-
* The env var is re-read on every call so tests and the CLI can swap the
|
|
29
|
-
* directory after this module has been imported.
|
|
30
|
-
*/
|
|
31
|
-
export function getDataDir(): string {
|
|
32
|
-
migrateLegacyDataDirs();
|
|
33
|
-
return resolveDataDir(existsSync);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type PathExists = (path: string) => boolean;
|
|
37
|
-
type DataDirEnv = Readonly<Record<string, string | undefined>>;
|
|
38
|
-
|
|
39
|
-
function hasDataDirState(pathExists: PathExists, dir: string): boolean {
|
|
40
|
-
return (
|
|
41
|
-
pathExists(join(dir, "providers.json")) ||
|
|
42
|
-
pathExists(join(dir, "config.json")) ||
|
|
43
|
-
pathExists(join(dir, "logs")) ||
|
|
44
|
-
pathExists(join(dir, "chunks"))
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function defaultBaseDir(
|
|
49
|
-
env: DataDirEnv = process.env,
|
|
50
|
-
platform: typeof process.platform = process.platform,
|
|
51
|
-
): string {
|
|
52
|
-
return platform === "win32"
|
|
53
|
-
? (env["USERPROFILE"] ?? env["APPDATA"] ?? "C:\\")
|
|
54
|
-
: (env["HOME"] ?? "/tmp");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function resolveDataDir(
|
|
58
|
-
_pathExists: PathExists,
|
|
59
|
-
env: DataDirEnv = process.env,
|
|
60
|
-
platform: typeof process.platform = process.platform,
|
|
61
|
-
): string {
|
|
62
|
-
const base = defaultBaseDir(env, platform);
|
|
63
|
-
|
|
64
|
-
const dirEnv = env["AGENT_INSPECTOR_DATA_DIR"];
|
|
65
|
-
if (dirEnv !== undefined && dirEnv !== "") {
|
|
66
|
-
return isAbsolute(dirEnv) ? dirEnv : join(base, dirEnv);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Backward-compat: the runtime config historically read
|
|
70
|
-
// `AGENT_INSPECTOR_CONFIG_DIR` as a directory *name* (relative to base).
|
|
71
|
-
// Honour it so existing CLI scripts keep working.
|
|
72
|
-
const legacyDirEnv = env["AGENT_INSPECTOR_CONFIG_DIR"];
|
|
73
|
-
if (legacyDirEnv !== undefined && legacyDirEnv !== "") {
|
|
74
|
-
return isAbsolute(legacyDirEnv) ? legacyDirEnv : join(base, legacyDirEnv);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return join(base, CURRENT_DATA_DIR_NAME);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* `true` when the user has explicitly asked for a non-default data
|
|
82
|
-
* directory. Used to skip the one-time legacy migration so we don't
|
|
83
|
-
* write to a path the user didn't choose.
|
|
84
|
-
*/
|
|
85
|
-
export function hasExplicitDataDir(): boolean {
|
|
86
|
-
return (
|
|
87
|
-
(process.env["AGENT_INSPECTOR_DATA_DIR"] !== undefined &&
|
|
88
|
-
process.env["AGENT_INSPECTOR_DATA_DIR"] !== "") ||
|
|
89
|
-
(process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== undefined &&
|
|
90
|
-
process.env["AGENT_INSPECTOR_CONFIG_DIR"] !== "")
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function migrateLegacyDataDirs(): void {
|
|
95
|
-
if (hasExplicitDataDir()) return;
|
|
96
|
-
|
|
97
|
-
const base = defaultBaseDir();
|
|
98
|
-
const currentDir = join(base, CURRENT_DATA_DIR_NAME);
|
|
99
|
-
if (hasDataDirState(existsSync, currentDir)) return;
|
|
100
|
-
|
|
101
|
-
for (const legacyName of LEGACY_DATA_DIR_NAMES) {
|
|
102
|
-
const legacyDir = join(base, legacyName);
|
|
103
|
-
if (!existsSync(legacyDir)) continue;
|
|
104
|
-
|
|
105
|
-
const stagingDir = join(
|
|
106
|
-
base,
|
|
107
|
-
`${CURRENT_DATA_DIR_NAME}.migration-${String(process.pid)}-${String(Date.now())}`,
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
// cpSync may dereference junctions on some Windows runtimes. Reject the source tree before
|
|
112
|
-
// copying and recheck it afterwards so migration never imports through an indirection.
|
|
113
|
-
if (!isPathTreeFreeOfIndirectionSync(legacyDir)) return;
|
|
114
|
-
if (existsSync(currentDir)) {
|
|
115
|
-
// Never merge legacy state into an unknown or partially initialized destination. A caller
|
|
116
|
-
// can inspect it and retry after resolving the conflict; the authoritative legacy source
|
|
117
|
-
// remains untouched.
|
|
118
|
-
if (readdirSync(currentDir).length > 0) return;
|
|
119
|
-
rmSync(currentDir, { recursive: true, force: false });
|
|
120
|
-
}
|
|
121
|
-
if (!ensurePrivateDirectorySync(stagingDir)) return;
|
|
122
|
-
for (const entry of readdirSync(legacyDir)) {
|
|
123
|
-
cpSync(join(legacyDir, entry), join(stagingDir, entry), {
|
|
124
|
-
recursive: true,
|
|
125
|
-
force: false,
|
|
126
|
-
errorOnExist: true,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
if (!isPathTreeFreeOfIndirectionSync(legacyDir)) {
|
|
130
|
-
rmSync(stagingDir, { recursive: true, force: true });
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (!securePrivateTreeSync(stagingDir)) {
|
|
134
|
-
rmSync(stagingDir, { recursive: true, force: true });
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
renameSync(stagingDir, currentDir);
|
|
138
|
-
rmSync(legacyDir, { recursive: true, force: true });
|
|
139
|
-
return;
|
|
140
|
-
} catch {
|
|
141
|
-
// A failed staged migration never deletes the legacy source. Remove only this attempt's
|
|
142
|
-
// private sibling and retry on a later startup.
|
|
143
|
-
if (existsSync(stagingDir)) rmSync(stagingDir, { recursive: true, force: true });
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
ECOSYSTEM_EXECUTION_CONFIRMATION_PHRASE,
|
|
4
|
-
type EcosystemExecutionConfirmation,
|
|
5
|
-
type EcosystemExecutionPlan,
|
|
6
|
-
type EcosystemTaskAction,
|
|
7
|
-
} from "../lib/ecosystemContract";
|
|
8
|
-
import { logger } from "./logger";
|
|
9
|
-
|
|
10
|
-
const CONFIRMATION_TTL_MS = 2 * 60 * 1000;
|
|
11
|
-
const MAX_PENDING_CONFIRMATIONS = 100;
|
|
12
|
-
|
|
13
|
-
type ConfirmationRecord = {
|
|
14
|
-
confirmation: EcosystemExecutionConfirmation;
|
|
15
|
-
expiresAtMs: number;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type ConfirmationFailureReason =
|
|
19
|
-
| "missing_or_invalid_confirmation"
|
|
20
|
-
| "expired_confirmation"
|
|
21
|
-
| "mismatched_confirmation";
|
|
22
|
-
|
|
23
|
-
type ConsumeEcosystemConfirmationResult =
|
|
24
|
-
| { ok: true; plan: EcosystemExecutionPlan }
|
|
25
|
-
| { ok: false; reason: ConfirmationFailureReason };
|
|
26
|
-
|
|
27
|
-
type EcosystemAuditEvent = "confirmation_issued" | "confirmation_rejected" | "execution_started";
|
|
28
|
-
type EcosystemAuditReason =
|
|
29
|
-
| "none"
|
|
30
|
-
| "invalid_request"
|
|
31
|
-
| "unknown_target"
|
|
32
|
-
| "missing_or_invalid_confirmation"
|
|
33
|
-
| "expired_confirmation"
|
|
34
|
-
| "mismatched_confirmation"
|
|
35
|
-
| "task_unavailable";
|
|
36
|
-
|
|
37
|
-
const confirmations = new Map<string, ConfirmationRecord>();
|
|
38
|
-
|
|
39
|
-
function pruneConfirmations(nowMs: number): void {
|
|
40
|
-
for (const [nonce, record] of confirmations) {
|
|
41
|
-
if (record.expiresAtMs <= nowMs) confirmations.delete(nonce);
|
|
42
|
-
}
|
|
43
|
-
while (confirmations.size >= MAX_PENDING_CONFIRMATIONS) {
|
|
44
|
-
const oldestNonce = confirmations.keys().next().value;
|
|
45
|
-
if (typeof oldestNonce !== "string") return;
|
|
46
|
-
confirmations.delete(oldestNonce);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function safeAuditTarget(targetId: string | null): string {
|
|
51
|
-
if (targetId === null || !/^[a-z0-9-]{1,64}$/.test(targetId)) return "unrecognized";
|
|
52
|
-
return targetId;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function auditEcosystemExecution(
|
|
56
|
-
event: EcosystemAuditEvent,
|
|
57
|
-
action: EcosystemTaskAction,
|
|
58
|
-
targetId: string | null,
|
|
59
|
-
reason: EcosystemAuditReason,
|
|
60
|
-
): void {
|
|
61
|
-
logger.info(
|
|
62
|
-
`[security-audit] event=${event} action=${action} target=${safeAuditTarget(targetId)} reason=${reason}`,
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function createEcosystemExecutionConfirmation(
|
|
67
|
-
plan: EcosystemExecutionPlan,
|
|
68
|
-
nowMs = Date.now(),
|
|
69
|
-
): EcosystemExecutionConfirmation {
|
|
70
|
-
pruneConfirmations(nowMs);
|
|
71
|
-
const expiresAtMs = nowMs + CONFIRMATION_TTL_MS;
|
|
72
|
-
const confirmation: EcosystemExecutionConfirmation = {
|
|
73
|
-
nonce: randomUUID(),
|
|
74
|
-
phrase: ECOSYSTEM_EXECUTION_CONFIRMATION_PHRASE,
|
|
75
|
-
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
76
|
-
plan,
|
|
77
|
-
};
|
|
78
|
-
confirmations.set(confirmation.nonce, { confirmation, expiresAtMs });
|
|
79
|
-
return confirmation;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function consumeEcosystemExecutionConfirmation(
|
|
83
|
-
nonce: string,
|
|
84
|
-
phrase: string,
|
|
85
|
-
expectedPlan: EcosystemExecutionPlan,
|
|
86
|
-
nowMs = Date.now(),
|
|
87
|
-
): ConsumeEcosystemConfirmationResult {
|
|
88
|
-
const record = confirmations.get(nonce);
|
|
89
|
-
if (record === undefined) {
|
|
90
|
-
return { ok: false, reason: "missing_or_invalid_confirmation" };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
confirmations.delete(nonce);
|
|
94
|
-
if (record.expiresAtMs <= nowMs) return { ok: false, reason: "expired_confirmation" };
|
|
95
|
-
const reviewedPlan = record.confirmation.plan;
|
|
96
|
-
if (
|
|
97
|
-
phrase !== ECOSYSTEM_EXECUTION_CONFIRMATION_PHRASE ||
|
|
98
|
-
reviewedPlan.action !== expectedPlan.action ||
|
|
99
|
-
reviewedPlan.targetId !== expectedPlan.targetId ||
|
|
100
|
-
reviewedPlan.targetName !== expectedPlan.targetName ||
|
|
101
|
-
reviewedPlan.command !== expectedPlan.command ||
|
|
102
|
-
reviewedPlan.source !== expectedPlan.source ||
|
|
103
|
-
reviewedPlan.timeoutMs !== expectedPlan.timeoutMs ||
|
|
104
|
-
reviewedPlan.scope !== expectedPlan.scope
|
|
105
|
-
) {
|
|
106
|
-
return { ok: false, reason: "mismatched_confirmation" };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return { ok: true, plan: reviewedPlan };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function resetEcosystemExecutionConfirmationsForTests(): void {
|
|
113
|
-
confirmations.clear();
|
|
114
|
-
}
|