@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/cli.ts
DELETED
|
@@ -1,1433 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Buffer } from "node:buffer";
|
|
3
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
4
|
-
import { createConnection } from "node:net";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
7
|
-
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
8
|
-
import type { Readable, Writable } from "node:stream";
|
|
9
|
-
import packageJson from "../package.json";
|
|
10
|
-
import {
|
|
11
|
-
AGENT_INSPECTOR_BACKEND_ENV,
|
|
12
|
-
AGENT_INSPECTOR_BACKEND_HEADER,
|
|
13
|
-
BackendImplementationSchema,
|
|
14
|
-
parseBackendImplementation,
|
|
15
|
-
type BackendImplementation,
|
|
16
|
-
} from "./lib/backendImplementation.js";
|
|
17
|
-
import {
|
|
18
|
-
AGENT_INSPECTOR_BASE_PATH_ENV,
|
|
19
|
-
AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH_ENV,
|
|
20
|
-
appendBasePathToOrigin,
|
|
21
|
-
getConfiguredBasePath,
|
|
22
|
-
normalizePublicBasePath,
|
|
23
|
-
} from "./lib/basePath.js";
|
|
24
|
-
import {
|
|
25
|
-
AGENT_INSPECTOR_CLI_ENTRY_ENV,
|
|
26
|
-
managedInstanceShutdownMessage,
|
|
27
|
-
readManagedInstanceLaunch,
|
|
28
|
-
type ManagedInstanceLaunch,
|
|
29
|
-
} from "./lib/managedInstance.js";
|
|
30
|
-
import {
|
|
31
|
-
formatAccessHintLines,
|
|
32
|
-
isLoopbackHost,
|
|
33
|
-
isValidBindHost,
|
|
34
|
-
localUrlForPort,
|
|
35
|
-
networkUrlsForBindHost,
|
|
36
|
-
probeHostForBindHost,
|
|
37
|
-
urlForHost,
|
|
38
|
-
} from "./cli/networkHints.js";
|
|
39
|
-
import {
|
|
40
|
-
shouldSuppressServerOutputLine,
|
|
41
|
-
type ServerOutputFilterContext,
|
|
42
|
-
} from "./cli/startupOutput.js";
|
|
43
|
-
import {
|
|
44
|
-
resolvePreferredBackendImplementation,
|
|
45
|
-
resolveRustBackendPackage,
|
|
46
|
-
} from "./cli/rustBackendPackage.js";
|
|
47
|
-
import { handleNamedInstanceApi } from "./cli/instanceApi.js";
|
|
48
|
-
import {
|
|
49
|
-
closeIdentityProxyGracefully,
|
|
50
|
-
forceCloseIdentityProxy,
|
|
51
|
-
parseUiOriginAllowlist,
|
|
52
|
-
startIdentityProxy,
|
|
53
|
-
stopIdentityProxyAdmission,
|
|
54
|
-
type ManagedInstanceControlOptions,
|
|
55
|
-
} from "./proxy/identityProxy.js";
|
|
56
|
-
import { findPidsByPort, killPid, openUrlCommand } from "./proxy/platformCommands.js";
|
|
57
|
-
import { resolveDataDir } from "./proxy/dataDir.js";
|
|
58
|
-
|
|
59
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
60
|
-
const __dirname = dirname(__filename);
|
|
61
|
-
|
|
62
|
-
// v3 defaults the public entrypoint to 9527. The legacy 9527 alias can be
|
|
63
|
-
// enabled explicitly for older AI-tool and MCP configs. The TanStack Start
|
|
64
|
-
// runtime stays on a private upstream port and public ports reverse-proxy to
|
|
65
|
-
// that same runtime, so logs, sessions, and MCP state remain unified.
|
|
66
|
-
const DEFAULT_PORT = 9527;
|
|
67
|
-
const LEGACY_ALIAS_PORT = 9527;
|
|
68
|
-
const DEFAULT_UPSTREAM_PORT = 9529;
|
|
69
|
-
const DEFAULT_BIND_HOST = "127.0.0.1";
|
|
70
|
-
const LOCAL_PROBE_TIMEOUT_MS = 5000;
|
|
71
|
-
const BRANDED_WINDOWS_RUNTIME_EXE = "agent-inspector.exe";
|
|
72
|
-
const DEFAULT_CAPTURE_MODE = "simple";
|
|
73
|
-
const DEFAULT_SHUTDOWN_DEADLINE_MS = 15_000;
|
|
74
|
-
const SUPERVISOR_SHUTDOWN_GRACE_MS = 2_000;
|
|
75
|
-
const FORCE_RESTART_PORT_RELEASE_TIMEOUT_MS = 10_000;
|
|
76
|
-
const FORCE_RESTART_PORT_RELEASE_POLL_MS = 100;
|
|
77
|
-
const TYPESCRIPT_STARTUP_READY_TIMEOUT_MS = 10_000;
|
|
78
|
-
const RUST_STARTUP_READY_TIMEOUT_MS = 60_000;
|
|
79
|
-
const AGENT_INSPECTOR_BACKGROUND_SUPERVISOR_ENV = "AGENT_INSPECTOR_BACKGROUND_SUPERVISOR";
|
|
80
|
-
|
|
81
|
-
type CaptureMode = "simple" | "full";
|
|
82
|
-
|
|
83
|
-
process.title = "Agent Inspector";
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Subcommand router. The legacy one-liner UX (`agent-inspector` with no args,
|
|
87
|
-
* or `agent-inspector start`) starts the proxy. `agent-inspector onboard` runs
|
|
88
|
-
* the guided skill install. Unknown subcommands fall back to the legacy
|
|
89
|
-
* behavior so a stray positional arg doesn't break an old invocation.
|
|
90
|
-
*/
|
|
91
|
-
const subcommand = process.argv[2];
|
|
92
|
-
|
|
93
|
-
function printGlobalHelp(): void {
|
|
94
|
-
console.log(`agent-inspector ${packageJson.version} - Agent observability and knowledge capture
|
|
95
|
-
|
|
96
|
-
Usage:
|
|
97
|
-
agent-inspector [start] [options]
|
|
98
|
-
agent-inspector instance <start|list|status|connect|stop|restart> ...
|
|
99
|
-
agent-inspector alias <list|add|remove> ...
|
|
100
|
-
agent-inspector onboard [options]
|
|
101
|
-
agent-inspector doctor [options]
|
|
102
|
-
|
|
103
|
-
Global options:
|
|
104
|
-
-h, --help Show this help without starting a runtime
|
|
105
|
-
-v, --version Print the package version
|
|
106
|
-
|
|
107
|
-
Start options:
|
|
108
|
-
--backend <typescript|rust> Select the backend implementation (default: rust when native package is available)
|
|
109
|
-
--backend-only, --headless Start the API, proxy, and MCP runtime without serving the UI
|
|
110
|
-
--with-ui Serve the independently built UI through the protected ingress
|
|
111
|
-
--port, -p <port> Public ingress port (default: 9527)
|
|
112
|
-
--host, -H <host> Bind host (default: 127.0.0.1)
|
|
113
|
-
--background Start a hidden supervisor and return after readiness
|
|
114
|
-
--no-open Do not open the UI after startup
|
|
115
|
-
|
|
116
|
-
Run \`agent-inspector instance --help\`, \`agent-inspector onboard --help\`, or
|
|
117
|
-
\`agent-inspector doctor --help\` for command-specific options.`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
switch (subcommand) {
|
|
121
|
-
case "--help":
|
|
122
|
-
case "-h":
|
|
123
|
-
printGlobalHelp();
|
|
124
|
-
process.exitCode = 0;
|
|
125
|
-
break;
|
|
126
|
-
case "--version":
|
|
127
|
-
case "-v":
|
|
128
|
-
console.log(packageJson.version);
|
|
129
|
-
process.exitCode = 0;
|
|
130
|
-
break;
|
|
131
|
-
case "onboard": {
|
|
132
|
-
const { runOnboard } = await import("./cli/onboard.js");
|
|
133
|
-
const code = await runOnboard(process.argv.slice(3));
|
|
134
|
-
process.exit(code);
|
|
135
|
-
}
|
|
136
|
-
case "doctor": {
|
|
137
|
-
const { runDoctor } = await import("./cli/doctor.js");
|
|
138
|
-
const code = await runDoctor(process.argv.slice(3));
|
|
139
|
-
process.exit(code);
|
|
140
|
-
}
|
|
141
|
-
case "alias": {
|
|
142
|
-
const { runAlias } = await import("./cli/alias.js");
|
|
143
|
-
const code = await runAlias(process.argv.slice(3));
|
|
144
|
-
process.exit(code);
|
|
145
|
-
}
|
|
146
|
-
case "instance": {
|
|
147
|
-
const { runInstance } = await import("./cli/instance.js");
|
|
148
|
-
const code = await runInstance(process.argv.slice(3));
|
|
149
|
-
process.exitCode = code;
|
|
150
|
-
break;
|
|
151
|
-
}
|
|
152
|
-
case undefined:
|
|
153
|
-
await runStart(process.argv.slice(2));
|
|
154
|
-
break;
|
|
155
|
-
case "start":
|
|
156
|
-
await runStart(process.argv.slice(3));
|
|
157
|
-
break;
|
|
158
|
-
default:
|
|
159
|
-
if (subcommand.startsWith("-")) {
|
|
160
|
-
await runStart(process.argv.slice(2));
|
|
161
|
-
} else {
|
|
162
|
-
console.error(`agent-inspector: unknown command ${subcommand}`);
|
|
163
|
-
console.error("Run `agent-inspector --help` for supported commands.");
|
|
164
|
-
process.exitCode = 2;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// -----------------------------------------------------------------------------
|
|
169
|
-
// Legacy `start` behavior — start the proxy on the configured port. Extracted
|
|
170
|
-
// into a function so the router above can keep the top-level flow readable.
|
|
171
|
-
// -----------------------------------------------------------------------------
|
|
172
|
-
async function isInspectorHealthy(port: number, host?: string, basePath = ""): Promise<boolean> {
|
|
173
|
-
const controller = new AbortController();
|
|
174
|
-
const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
|
|
175
|
-
try {
|
|
176
|
-
const response = await fetch(
|
|
177
|
-
appendBasePathToOrigin(urlForHost(port, probeHostForBindHost(host)), "/api/health", basePath),
|
|
178
|
-
{
|
|
179
|
-
cache: "no-store",
|
|
180
|
-
signal: controller.signal,
|
|
181
|
-
},
|
|
182
|
-
);
|
|
183
|
-
return response.ok;
|
|
184
|
-
} catch {
|
|
185
|
-
return false;
|
|
186
|
-
} finally {
|
|
187
|
-
clearTimeout(timeout);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async function getRunningCaptureMode(
|
|
192
|
-
port: number,
|
|
193
|
-
host?: string,
|
|
194
|
-
basePath = "",
|
|
195
|
-
): Promise<CaptureMode | null> {
|
|
196
|
-
const controller = new AbortController();
|
|
197
|
-
const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
|
|
198
|
-
try {
|
|
199
|
-
const response = await fetch(
|
|
200
|
-
appendBasePathToOrigin(urlForHost(port, probeHostForBindHost(host)), "/api/config", basePath),
|
|
201
|
-
{
|
|
202
|
-
cache: "no-store",
|
|
203
|
-
signal: controller.signal,
|
|
204
|
-
},
|
|
205
|
-
);
|
|
206
|
-
if (!response.ok) return null;
|
|
207
|
-
const raw: unknown = await response.json();
|
|
208
|
-
return readCaptureMode(raw);
|
|
209
|
-
} catch {
|
|
210
|
-
return null;
|
|
211
|
-
} finally {
|
|
212
|
-
clearTimeout(timeout);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function getRunningBackend(
|
|
217
|
-
port: number,
|
|
218
|
-
host?: string,
|
|
219
|
-
basePath = "",
|
|
220
|
-
): Promise<BackendImplementation | null> {
|
|
221
|
-
const controller = new AbortController();
|
|
222
|
-
const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
|
|
223
|
-
try {
|
|
224
|
-
const response = await fetch(
|
|
225
|
-
appendBasePathToOrigin(urlForHost(port, probeHostForBindHost(host)), "/api/health", basePath),
|
|
226
|
-
{
|
|
227
|
-
cache: "no-store",
|
|
228
|
-
signal: controller.signal,
|
|
229
|
-
},
|
|
230
|
-
);
|
|
231
|
-
if (!response.ok) return null;
|
|
232
|
-
const header = response.headers.get(AGENT_INSPECTOR_BACKEND_HEADER);
|
|
233
|
-
if (header === null) return "typescript";
|
|
234
|
-
const parsed = BackendImplementationSchema.safeParse(header.trim().toLowerCase());
|
|
235
|
-
return parsed.success ? parsed.data : null;
|
|
236
|
-
} catch {
|
|
237
|
-
return null;
|
|
238
|
-
} finally {
|
|
239
|
-
clearTimeout(timeout);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function isPortAcceptingConnections(port: number, host?: string): Promise<boolean> {
|
|
244
|
-
return new Promise((resolve) => {
|
|
245
|
-
const socket = createConnection({ host: probeHostForBindHost(host), port });
|
|
246
|
-
const finish = (value: boolean): void => {
|
|
247
|
-
socket.removeAllListeners();
|
|
248
|
-
socket.destroy();
|
|
249
|
-
resolve(value);
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
socket.setTimeout(LOCAL_PROBE_TIMEOUT_MS);
|
|
253
|
-
socket.once("connect", () => finish(true));
|
|
254
|
-
socket.once("timeout", () => finish(false));
|
|
255
|
-
socket.once("error", () => finish(false));
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function sleep(ms: number): Promise<void> {
|
|
260
|
-
return new Promise((resolve) => {
|
|
261
|
-
setTimeout(resolve, ms);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async function waitForPortToRelease(port: number, host?: string): Promise<boolean> {
|
|
266
|
-
const deadline = Date.now() + FORCE_RESTART_PORT_RELEASE_TIMEOUT_MS;
|
|
267
|
-
while (Date.now() < deadline) {
|
|
268
|
-
if (!(await isPortAcceptingConnections(port, host))) return true;
|
|
269
|
-
await sleep(FORCE_RESTART_PORT_RELEASE_POLL_MS);
|
|
270
|
-
}
|
|
271
|
-
return !(await isPortAcceptingConnections(port, host));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async function waitForPortsToRelease(ports: Iterable<number>, host?: string): Promise<boolean> {
|
|
275
|
-
let released = true;
|
|
276
|
-
for (const port of ports) {
|
|
277
|
-
if (!(await waitForPortToRelease(port, host))) {
|
|
278
|
-
const remaining = findPidsByPort(port);
|
|
279
|
-
const suffix =
|
|
280
|
-
remaining.length === 0 ? "" : ` Remaining listener PID(s): ${remaining.join(", ")}.`;
|
|
281
|
-
console.error(
|
|
282
|
-
`Port ${String(port)} did not release after ${String(
|
|
283
|
-
FORCE_RESTART_PORT_RELEASE_TIMEOUT_MS,
|
|
284
|
-
)}ms.${suffix}`,
|
|
285
|
-
);
|
|
286
|
-
released = false;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return released;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function startupReadyTimeoutMs(backend: BackendImplementation): number {
|
|
293
|
-
const configured = Number(process.env["AGENT_INSPECTOR_STARTUP_READY_TIMEOUT_MS"] ?? "");
|
|
294
|
-
if (Number.isInteger(configured) && configured > 0) return configured;
|
|
295
|
-
return backend === "rust" ? RUST_STARTUP_READY_TIMEOUT_MS : TYPESCRIPT_STARTUP_READY_TIMEOUT_MS;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
async function waitForInspectorHealthy(
|
|
299
|
-
port: number,
|
|
300
|
-
timeoutMs: number,
|
|
301
|
-
host?: string,
|
|
302
|
-
basePath = "",
|
|
303
|
-
): Promise<boolean> {
|
|
304
|
-
const start = Date.now();
|
|
305
|
-
while (Date.now() - start < timeoutMs) {
|
|
306
|
-
if (await isInspectorHealthy(port, host, basePath)) return true;
|
|
307
|
-
await sleep(250);
|
|
308
|
-
}
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function openBrowser(targetUrl: string): void {
|
|
313
|
-
const command = openUrlCommand(targetUrl);
|
|
314
|
-
if (command === null) return;
|
|
315
|
-
const browserProcess = spawn(command.command, command.args, {
|
|
316
|
-
stdio: "ignore",
|
|
317
|
-
detached: true,
|
|
318
|
-
windowsHide: true,
|
|
319
|
-
});
|
|
320
|
-
browserProcess.once("error", () => {
|
|
321
|
-
// Opening a browser is best-effort. Minimal Linux/container images may not
|
|
322
|
-
// include xdg-open, and that should not make the CLI look unhealthy.
|
|
323
|
-
});
|
|
324
|
-
browserProcess.unref();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function waitForProcessExit(child: ChildProcess): Promise<number> {
|
|
328
|
-
return new Promise((resolve) => {
|
|
329
|
-
child.once("exit", (code) => {
|
|
330
|
-
resolve(code ?? 1);
|
|
331
|
-
});
|
|
332
|
-
child.once("error", () => {
|
|
333
|
-
resolve(1);
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
function pipeServerOutput(child: ChildProcess, context: ServerOutputFilterContext): void {
|
|
339
|
-
pipeServerOutputStream(child.stdout, process.stdout, context);
|
|
340
|
-
pipeServerOutputStream(child.stderr, process.stderr, context);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
type BackgroundSupervisorLogs = {
|
|
344
|
-
stdoutPath: string;
|
|
345
|
-
stderrPath: string;
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
function createBackgroundSupervisorLogs(
|
|
349
|
-
env: Readonly<Record<string, string | undefined>>,
|
|
350
|
-
): BackgroundSupervisorLogs | null {
|
|
351
|
-
try {
|
|
352
|
-
const logsDir = join(resolveDataDir(existsSync, env), "logs");
|
|
353
|
-
mkdirSync(logsDir, { recursive: true });
|
|
354
|
-
const stdoutPath = join(logsDir, "backend.stdout.log");
|
|
355
|
-
const stderrPath = join(logsDir, "backend.stderr.log");
|
|
356
|
-
const header = `\n[${new Date().toISOString()}] Agent Inspector backend process started\n`;
|
|
357
|
-
appendFileSync(stdoutPath, header, { encoding: "utf8" });
|
|
358
|
-
appendFileSync(stderrPath, header, { encoding: "utf8" });
|
|
359
|
-
return { stdoutPath, stderrPath };
|
|
360
|
-
} catch (error) {
|
|
361
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
362
|
-
process.stderr.write(`Failed to open Agent Inspector background logs: ${message}\n`);
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function pipeServerOutputStream(
|
|
368
|
-
stream: Readable | null,
|
|
369
|
-
target: Writable,
|
|
370
|
-
context: ServerOutputFilterContext,
|
|
371
|
-
): void {
|
|
372
|
-
if (stream === null) return;
|
|
373
|
-
|
|
374
|
-
let buffered = "";
|
|
375
|
-
stream.on("data", (chunk: unknown) => {
|
|
376
|
-
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
377
|
-
const lines = `${buffered}${text}`.split(/\r?\n/);
|
|
378
|
-
buffered = lines.pop() ?? "";
|
|
379
|
-
for (const line of lines) {
|
|
380
|
-
if (!shouldSuppressServerOutputLine(line, context)) {
|
|
381
|
-
target.write(`${line}\n`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
stream.on("end", () => {
|
|
386
|
-
if (buffered.length === 0) return;
|
|
387
|
-
if (!shouldSuppressServerOutputLine(buffered, context)) {
|
|
388
|
-
target.write(buffered);
|
|
389
|
-
}
|
|
390
|
-
buffered = "";
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function appendBackgroundLog(path: string, text: string): void {
|
|
395
|
-
try {
|
|
396
|
-
appendFileSync(path, text, { encoding: "utf8" });
|
|
397
|
-
} catch {
|
|
398
|
-
// Background supervisor output is best-effort: startup must not fail just
|
|
399
|
-
// because a diagnostic log file is temporarily unavailable.
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
function pipeServerOutputStreamToFile(
|
|
404
|
-
stream: Readable | null,
|
|
405
|
-
targetPath: string,
|
|
406
|
-
context: ServerOutputFilterContext,
|
|
407
|
-
): void {
|
|
408
|
-
if (stream === null) return;
|
|
409
|
-
|
|
410
|
-
let buffered = "";
|
|
411
|
-
stream.on("data", (chunk: unknown) => {
|
|
412
|
-
const text = Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
413
|
-
const lines = `${buffered}${text}`.split(/\r?\n/);
|
|
414
|
-
buffered = lines.pop() ?? "";
|
|
415
|
-
for (const line of lines) {
|
|
416
|
-
if (!shouldSuppressServerOutputLine(line, context)) {
|
|
417
|
-
appendBackgroundLog(targetPath, `${line}\n`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
stream.on("end", () => {
|
|
422
|
-
if (buffered.length === 0) return;
|
|
423
|
-
if (!shouldSuppressServerOutputLine(buffered, context)) {
|
|
424
|
-
appendBackgroundLog(targetPath, buffered);
|
|
425
|
-
}
|
|
426
|
-
buffered = "";
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
type ServerCommand = {
|
|
431
|
-
command: string;
|
|
432
|
-
args: string[];
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
type ServerCommandResolution =
|
|
436
|
-
| { kind: "success"; command: ServerCommand }
|
|
437
|
-
| { kind: "error"; message: string };
|
|
438
|
-
|
|
439
|
-
function resolveServerCommand(
|
|
440
|
-
outputDir: string,
|
|
441
|
-
serverPath: string,
|
|
442
|
-
backend: BackendImplementation,
|
|
443
|
-
): ServerCommandResolution {
|
|
444
|
-
if (backend === "rust") {
|
|
445
|
-
const resolved = resolveRustBackendPackage({ rootVersion: packageJson.version });
|
|
446
|
-
return resolved.kind === "error"
|
|
447
|
-
? {
|
|
448
|
-
kind: "error",
|
|
449
|
-
message: `Cannot start the Rust backend (${resolved.error.code}): ${resolved.error.message}`,
|
|
450
|
-
}
|
|
451
|
-
: {
|
|
452
|
-
kind: "success",
|
|
453
|
-
command: { command: resolved.value.command, args: [...resolved.value.args] },
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
const brandedRuntime = join(outputDir, BRANDED_WINDOWS_RUNTIME_EXE);
|
|
457
|
-
return {
|
|
458
|
-
kind: "success",
|
|
459
|
-
command:
|
|
460
|
-
process.platform === "win32" && existsSync(brandedRuntime)
|
|
461
|
-
? { command: brandedRuntime, args: [serverPath] }
|
|
462
|
-
: { command: process.execPath, args: [serverPath] },
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function parseCaptureMode(value: string | undefined): CaptureMode | null {
|
|
467
|
-
switch (value) {
|
|
468
|
-
case undefined:
|
|
469
|
-
return null;
|
|
470
|
-
case "simple":
|
|
471
|
-
return "simple";
|
|
472
|
-
case "full":
|
|
473
|
-
return "full";
|
|
474
|
-
default:
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function readCaptureMode(raw: unknown): CaptureMode | null {
|
|
480
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
481
|
-
const descriptor = Object.getOwnPropertyDescriptor(raw, "captureMode");
|
|
482
|
-
const value: unknown = descriptor?.value;
|
|
483
|
-
return typeof value === "string" ? parseCaptureMode(value) : null;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function printAccessHints(
|
|
487
|
-
port: number,
|
|
488
|
-
host: string | undefined,
|
|
489
|
-
basePath: string,
|
|
490
|
-
serveUi: boolean,
|
|
491
|
-
publicOrigin: string | undefined,
|
|
492
|
-
): void {
|
|
493
|
-
const networkUrls = networkUrlsForBindHost(port, host);
|
|
494
|
-
for (const line of formatAccessHintLines(port, networkUrls, host, basePath, serveUi)) {
|
|
495
|
-
console.log(line);
|
|
496
|
-
}
|
|
497
|
-
if (publicOrigin !== undefined) {
|
|
498
|
-
const publicBaseUrl = appendBasePathToOrigin(publicOrigin, "/", basePath);
|
|
499
|
-
if (serveUi) console.log(` Public UI: ${publicBaseUrl}`);
|
|
500
|
-
console.log(` Public control Base: ${publicBaseUrl}`);
|
|
501
|
-
console.log(` Public agent Base: ${publicBaseUrl}`);
|
|
502
|
-
console.log(` Public REST API: ${appendBasePathToOrigin(publicOrigin, "/api", basePath)}`);
|
|
503
|
-
console.log(` Public proxy: ${appendBasePathToOrigin(publicOrigin, "/proxy", basePath)}`);
|
|
504
|
-
console.log(` Public MCP: ${appendBasePathToOrigin(publicOrigin, "/api/mcp", basePath)}`);
|
|
505
|
-
}
|
|
506
|
-
if (!isLoopbackHost(host)) {
|
|
507
|
-
const proxyTokenConfigured = (process.env["AGENT_INSPECTOR_PROXY_TOKEN"] ?? "") !== "";
|
|
508
|
-
const legacyProxyEnabled =
|
|
509
|
-
process.env["AGENT_INSPECTOR_ALLOW_UNAUTHENTICATED_REMOTE_PROXY"] === "1";
|
|
510
|
-
if (proxyTokenConfigured) {
|
|
511
|
-
console.log(" Remote proxy: dedicated token authentication enabled");
|
|
512
|
-
} else if (legacyProxyEnabled) {
|
|
513
|
-
console.warn(" Remote proxy: UNAUTHENTICATED compatibility mode enabled");
|
|
514
|
-
} else {
|
|
515
|
-
console.warn(" Remote proxy: blocked until AGENT_INSPECTOR_PROXY_TOKEN is configured");
|
|
516
|
-
}
|
|
517
|
-
if (process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] === "1") {
|
|
518
|
-
console.warn(" Control plane: REMOTE ACCESS ENABLED by administrator override");
|
|
519
|
-
} else {
|
|
520
|
-
console.log(" Control plane: local only by default");
|
|
521
|
-
if ((process.env["AGENT_INSPECTOR_CONTROL_TOKEN"] ?? "") !== "") {
|
|
522
|
-
console.log(" Remote API: Bearer token authentication enabled");
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function readPositiveIntegerEnv(name: string): number | undefined {
|
|
529
|
-
const raw = process.env[name];
|
|
530
|
-
if (raw === undefined || raw.trim().length === 0) return undefined;
|
|
531
|
-
const parsed = Number(raw);
|
|
532
|
-
if (Number.isSafeInteger(parsed) && parsed > 0) return parsed;
|
|
533
|
-
console.warn(`Ignoring invalid ${name}=${raw}; expected a positive integer.`);
|
|
534
|
-
return undefined;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function normalizeExplicitPublicOrigin(value: string | undefined): string | null | undefined {
|
|
538
|
-
if (value === undefined || value.trim().length === 0) return undefined;
|
|
539
|
-
try {
|
|
540
|
-
const parsed = new URL(value.trim());
|
|
541
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
542
|
-
if (parsed.username.length > 0 || parsed.password.length > 0) return null;
|
|
543
|
-
if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) return null;
|
|
544
|
-
return parsed.origin;
|
|
545
|
-
} catch {
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
type BackgroundSupervisorOptions = {
|
|
551
|
-
backend: BackendImplementation;
|
|
552
|
-
port: number;
|
|
553
|
-
host: string | undefined;
|
|
554
|
-
configDir: string | undefined;
|
|
555
|
-
providersJson: string | undefined;
|
|
556
|
-
captureMode: CaptureMode;
|
|
557
|
-
enableIdentityProxy: boolean;
|
|
558
|
-
legacyAliasEnabled: boolean;
|
|
559
|
-
serveUi: boolean;
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
function buildBackgroundSupervisorArgs(options: BackgroundSupervisorOptions): string[] {
|
|
563
|
-
const args = [
|
|
564
|
-
"--no-open",
|
|
565
|
-
"--backend",
|
|
566
|
-
options.backend,
|
|
567
|
-
"--port",
|
|
568
|
-
String(options.port),
|
|
569
|
-
"--mode",
|
|
570
|
-
options.captureMode,
|
|
571
|
-
];
|
|
572
|
-
if (options.host !== undefined) {
|
|
573
|
-
args.push("--host", options.host);
|
|
574
|
-
}
|
|
575
|
-
if (options.configDir !== undefined) {
|
|
576
|
-
args.push("--config-dir", options.configDir);
|
|
577
|
-
}
|
|
578
|
-
if (options.providersJson !== undefined) {
|
|
579
|
-
args.push("--providers", options.providersJson);
|
|
580
|
-
}
|
|
581
|
-
if (!options.enableIdentityProxy) {
|
|
582
|
-
args.push("--no-identity-proxy");
|
|
583
|
-
}
|
|
584
|
-
if (options.legacyAliasEnabled) {
|
|
585
|
-
args.push("--legacy-port");
|
|
586
|
-
}
|
|
587
|
-
if (!options.serveUi) {
|
|
588
|
-
args.push("--backend-only");
|
|
589
|
-
}
|
|
590
|
-
return args;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function pickUpstreamPort(publicPort: number, legacyAliasPort: number | null): number {
|
|
594
|
-
const nearbyPort = publicPort + 2 <= 65535 ? publicPort + 2 : publicPort - 2;
|
|
595
|
-
const candidates =
|
|
596
|
-
publicPort === DEFAULT_PORT
|
|
597
|
-
? [DEFAULT_UPSTREAM_PORT, nearbyPort, LEGACY_ALIAS_PORT + 2]
|
|
598
|
-
: [nearbyPort, DEFAULT_UPSTREAM_PORT, LEGACY_ALIAS_PORT + 2];
|
|
599
|
-
for (const candidate of candidates) {
|
|
600
|
-
if (
|
|
601
|
-
Number.isInteger(candidate) &&
|
|
602
|
-
candidate > 0 &&
|
|
603
|
-
candidate <= 65535 &&
|
|
604
|
-
candidate !== publicPort &&
|
|
605
|
-
candidate !== legacyAliasPort
|
|
606
|
-
) {
|
|
607
|
-
return candidate;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
return DEFAULT_UPSTREAM_PORT;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/** Try to start an identity-proxy ingress. The primary caller treats a
|
|
614
|
-
* `null` result as fatal; only an optional compatibility alias may degrade. */
|
|
615
|
-
async function tryStartIdentityProxy(
|
|
616
|
-
identityPort: number,
|
|
617
|
-
upstreamPort: number,
|
|
618
|
-
listenHost: string,
|
|
619
|
-
upstreamHost: string,
|
|
620
|
-
managedInstance: ManagedInstanceControlOptions | undefined,
|
|
621
|
-
allowedUiOrigins: readonly string[],
|
|
622
|
-
uiDirectory: string | undefined,
|
|
623
|
-
publicOrigin: string | undefined,
|
|
624
|
-
backend: BackendImplementation,
|
|
625
|
-
): Promise<import("node:http").Server | null> {
|
|
626
|
-
try {
|
|
627
|
-
const server = await startIdentityProxy({
|
|
628
|
-
listenPort: identityPort,
|
|
629
|
-
listenHost,
|
|
630
|
-
upstreamHost,
|
|
631
|
-
upstreamPort,
|
|
632
|
-
backendImplementation: backend,
|
|
633
|
-
restrictRemoteControl: !isLoopbackHost(listenHost),
|
|
634
|
-
allowRemoteControl: process.env["AGENT_INSPECTOR_ALLOW_REMOTE_CONTROL"] === "1",
|
|
635
|
-
controlToken: process.env["AGENT_INSPECTOR_CONTROL_TOKEN"],
|
|
636
|
-
proxyToken: process.env["AGENT_INSPECTOR_PROXY_TOKEN"],
|
|
637
|
-
allowUnauthenticatedRemoteProxy:
|
|
638
|
-
process.env["AGENT_INSPECTOR_ALLOW_UNAUTHENTICATED_REMOTE_PROXY"] === "1",
|
|
639
|
-
trustedHosts: (process.env["AGENT_INSPECTOR_TRUSTED_HOSTS"] ?? "")
|
|
640
|
-
.split(",")
|
|
641
|
-
.map((host) => host.trim())
|
|
642
|
-
.filter((host) => host.length > 0),
|
|
643
|
-
allowedUiOrigins,
|
|
644
|
-
uiDirectory,
|
|
645
|
-
publicOrigin,
|
|
646
|
-
maxRequestBytes: readPositiveIntegerEnv("AGENT_INSPECTOR_MAX_REQUEST_BYTES"),
|
|
647
|
-
requestTimeoutMs: readPositiveIntegerEnv("AGENT_INSPECTOR_REQUEST_TIMEOUT_MS"),
|
|
648
|
-
managedInstance,
|
|
649
|
-
instanceApiHandler: backend === "rust" ? handleNamedInstanceApi : undefined,
|
|
650
|
-
});
|
|
651
|
-
return server;
|
|
652
|
-
} catch (err) {
|
|
653
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
654
|
-
console.warn(`[identity-proxy] Could not listen on port ${identityPort}: ${message}.`);
|
|
655
|
-
return null;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
async function runStart(args: string[]): Promise<void> {
|
|
660
|
-
const managedLaunchResult = readManagedInstanceLaunch(process.env);
|
|
661
|
-
if (managedLaunchResult.kind === "invalid") {
|
|
662
|
-
console.error(managedLaunchResult.message);
|
|
663
|
-
process.exitCode = 1;
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const managedLaunch: ManagedInstanceLaunch | null =
|
|
667
|
-
managedLaunchResult.kind === "managed" ? managedLaunchResult.launch : null;
|
|
668
|
-
const envPort = process.env["AGENT_INSPECTOR_PORT"] ?? process.env["PORT"];
|
|
669
|
-
const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
|
|
670
|
-
const envHost = process.env["NITRO_HOST"] ?? process.env["HOST"];
|
|
671
|
-
const envMode =
|
|
672
|
-
process.env["AGENT_INSPECTOR_CAPTURE_MODE"] ?? process.env["AGENT_INSPECTOR_MODE"];
|
|
673
|
-
const rawEnvBackend = process.env[AGENT_INSPECTOR_BACKEND_ENV];
|
|
674
|
-
const envBackend =
|
|
675
|
-
rawEnvBackend === undefined ? undefined : parseBackendImplementation(rawEnvBackend);
|
|
676
|
-
if (envBackend === null) {
|
|
677
|
-
console.error(
|
|
678
|
-
`Invalid ${AGENT_INSPECTOR_BACKEND_ENV}. Use typescript or rust; no backend was started.`,
|
|
679
|
-
);
|
|
680
|
-
process.exitCode = 1;
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
let port = portDefault;
|
|
685
|
-
let host =
|
|
686
|
-
envHost !== undefined && envHost.trim().length > 0 ? envHost.trim() : DEFAULT_BIND_HOST;
|
|
687
|
-
let open = true;
|
|
688
|
-
let openWasSpecified = false;
|
|
689
|
-
let background = false;
|
|
690
|
-
let forceRestart = false;
|
|
691
|
-
let configDir: string | undefined;
|
|
692
|
-
let providersJson: string | undefined;
|
|
693
|
-
let captureMode: CaptureMode = DEFAULT_CAPTURE_MODE;
|
|
694
|
-
let captureModeWasSpecified = false;
|
|
695
|
-
let enableIdentityProxy = true;
|
|
696
|
-
let legacyAliasEnabled = false;
|
|
697
|
-
let serveUi = process.env["AGENT_INSPECTOR_BACKEND_ONLY"] !== "1";
|
|
698
|
-
let backend: BackendImplementation = envBackend ?? "typescript";
|
|
699
|
-
let backendWasSpecified = envBackend !== undefined;
|
|
700
|
-
|
|
701
|
-
if (envMode !== undefined && envMode !== "") {
|
|
702
|
-
const parsedMode = parseCaptureMode(envMode);
|
|
703
|
-
if (parsedMode === null) {
|
|
704
|
-
console.error(`Invalid capture mode: ${envMode}. Use simple or full.`);
|
|
705
|
-
process.exitCode = 1;
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
captureMode = parsedMode;
|
|
709
|
-
captureModeWasSpecified = true;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (host !== undefined && !isValidBindHost(host)) {
|
|
713
|
-
console.error(`Invalid host: ${host}. Use an IP or hostname without protocol/path.`);
|
|
714
|
-
process.exitCode = 1;
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
for (let i = 0; i < args.length; i++) {
|
|
719
|
-
const arg = args[i] ?? "";
|
|
720
|
-
if (arg.startsWith("--port=")) {
|
|
721
|
-
port = Number(arg.slice(arg.indexOf("=") + 1));
|
|
722
|
-
continue;
|
|
723
|
-
}
|
|
724
|
-
if (arg.startsWith("--host=")) {
|
|
725
|
-
const value = arg.slice(arg.indexOf("=") + 1).trim();
|
|
726
|
-
if (!isValidBindHost(value)) {
|
|
727
|
-
console.error(`Invalid host: ${value}. Use an IP or hostname without protocol/path.`);
|
|
728
|
-
process.exitCode = 1;
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
host = value;
|
|
732
|
-
continue;
|
|
733
|
-
}
|
|
734
|
-
if (arg.startsWith("--mode=") || arg.startsWith("--capture-mode=")) {
|
|
735
|
-
const value = arg.slice(arg.indexOf("=") + 1);
|
|
736
|
-
const parsedMode = parseCaptureMode(value);
|
|
737
|
-
if (parsedMode === null) {
|
|
738
|
-
console.error(`Invalid capture mode: ${value}. Use simple or full.`);
|
|
739
|
-
process.exitCode = 1;
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
captureMode = parsedMode;
|
|
743
|
-
captureModeWasSpecified = true;
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
if (arg.startsWith("--backend=")) {
|
|
747
|
-
const parsedBackend = BackendImplementationSchema.safeParse(
|
|
748
|
-
arg
|
|
749
|
-
.slice(arg.indexOf("=") + 1)
|
|
750
|
-
.trim()
|
|
751
|
-
.toLowerCase(),
|
|
752
|
-
);
|
|
753
|
-
if (!parsedBackend.success) {
|
|
754
|
-
console.error("Invalid backend implementation. Use typescript or rust.");
|
|
755
|
-
process.exitCode = 1;
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
backend = parsedBackend.data;
|
|
759
|
-
backendWasSpecified = true;
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
switch (arg) {
|
|
763
|
-
case "--port":
|
|
764
|
-
case "-p":
|
|
765
|
-
port = Number(args[i + 1]);
|
|
766
|
-
i++;
|
|
767
|
-
break;
|
|
768
|
-
case "--host":
|
|
769
|
-
case "-H": {
|
|
770
|
-
const value = args[i + 1]?.trim() ?? "";
|
|
771
|
-
if (!isValidBindHost(value)) {
|
|
772
|
-
console.error(`Invalid host: ${value}. Use an IP or hostname without protocol/path.`);
|
|
773
|
-
process.exitCode = 1;
|
|
774
|
-
return;
|
|
775
|
-
}
|
|
776
|
-
host = value;
|
|
777
|
-
i++;
|
|
778
|
-
break;
|
|
779
|
-
}
|
|
780
|
-
case "--backend": {
|
|
781
|
-
const parsedBackend = BackendImplementationSchema.safeParse(
|
|
782
|
-
args[i + 1]?.trim().toLowerCase(),
|
|
783
|
-
);
|
|
784
|
-
if (!parsedBackend.success) {
|
|
785
|
-
console.error("Invalid backend implementation. Use typescript or rust.");
|
|
786
|
-
process.exitCode = 1;
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
backend = parsedBackend.data;
|
|
790
|
-
backendWasSpecified = true;
|
|
791
|
-
i++;
|
|
792
|
-
break;
|
|
793
|
-
}
|
|
794
|
-
case "--no-open":
|
|
795
|
-
open = false;
|
|
796
|
-
openWasSpecified = true;
|
|
797
|
-
break;
|
|
798
|
-
case "--open":
|
|
799
|
-
open = true;
|
|
800
|
-
openWasSpecified = true;
|
|
801
|
-
break;
|
|
802
|
-
case "--force-restart":
|
|
803
|
-
case "--restart":
|
|
804
|
-
forceRestart = true;
|
|
805
|
-
break;
|
|
806
|
-
case "--background":
|
|
807
|
-
background = true;
|
|
808
|
-
break;
|
|
809
|
-
case "--backend-only":
|
|
810
|
-
case "--headless":
|
|
811
|
-
serveUi = false;
|
|
812
|
-
open = false;
|
|
813
|
-
break;
|
|
814
|
-
case "--with-ui":
|
|
815
|
-
serveUi = true;
|
|
816
|
-
break;
|
|
817
|
-
case "--config-dir":
|
|
818
|
-
configDir = args[i + 1];
|
|
819
|
-
i++;
|
|
820
|
-
break;
|
|
821
|
-
case "--providers":
|
|
822
|
-
providersJson = args[i + 1];
|
|
823
|
-
i++;
|
|
824
|
-
break;
|
|
825
|
-
case "--mode":
|
|
826
|
-
case "--capture-mode": {
|
|
827
|
-
const value = args[i + 1];
|
|
828
|
-
const parsedMode = parseCaptureMode(value);
|
|
829
|
-
if (parsedMode === null) {
|
|
830
|
-
console.error(`Invalid capture mode: ${String(value)}. Use simple or full.`);
|
|
831
|
-
process.exitCode = 1;
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
captureMode = parsedMode;
|
|
835
|
-
captureModeWasSpecified = true;
|
|
836
|
-
i++;
|
|
837
|
-
break;
|
|
838
|
-
}
|
|
839
|
-
case "--no-identity-proxy":
|
|
840
|
-
enableIdentityProxy = false;
|
|
841
|
-
break;
|
|
842
|
-
case "--identity-proxy-port": {
|
|
843
|
-
const value = args[i + 1];
|
|
844
|
-
const parsed = Number(value);
|
|
845
|
-
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
846
|
-
console.error(`Invalid identity-proxy port: ${String(value)}.`);
|
|
847
|
-
process.exitCode = 1;
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
|
-
port = parsed;
|
|
851
|
-
i++;
|
|
852
|
-
break;
|
|
853
|
-
}
|
|
854
|
-
case "--no-legacy-port":
|
|
855
|
-
legacyAliasEnabled = false;
|
|
856
|
-
break;
|
|
857
|
-
case "--legacy-port":
|
|
858
|
-
legacyAliasEnabled = true;
|
|
859
|
-
break;
|
|
860
|
-
case "--help":
|
|
861
|
-
case "-h":
|
|
862
|
-
printGlobalHelp();
|
|
863
|
-
return;
|
|
864
|
-
case "--version":
|
|
865
|
-
case "-v":
|
|
866
|
-
console.log(packageJson.version);
|
|
867
|
-
return;
|
|
868
|
-
default:
|
|
869
|
-
console.error(`agent-inspector start: unknown option ${arg}`);
|
|
870
|
-
console.error("Run `agent-inspector --help` for supported options.");
|
|
871
|
-
process.exitCode = 2;
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
877
|
-
console.error(`Invalid port: ${String(port)}. Use --port <1-65535>.`);
|
|
878
|
-
process.exitCode = 1;
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
if (!backendWasSpecified) {
|
|
883
|
-
backend =
|
|
884
|
-
managedLaunch?.backend ??
|
|
885
|
-
resolvePreferredBackendImplementation({ rootVersion: packageJson.version });
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (managedLaunch !== null && managedLaunch.backend !== backend) {
|
|
889
|
-
console.error(
|
|
890
|
-
`Managed instance backend mismatch: launch identity requires ${managedLaunch.backend}, but ${backend} was selected.`,
|
|
891
|
-
);
|
|
892
|
-
process.exitCode = 1;
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
if (!enableIdentityProxy && process.env["AGENT_INSPECTOR_ALLOW_UNPROTECTED_INGRESS"] !== "1") {
|
|
897
|
-
console.error(
|
|
898
|
-
"Refusing --no-identity-proxy because it also disables Host, CSRF, credential-scope, and first-hop budget enforcement. Keep the protected ingress enabled or set AGENT_INSPECTOR_ALLOW_UNPROTECTED_INGRESS=1 explicitly for temporary diagnostics.",
|
|
899
|
-
);
|
|
900
|
-
process.exitCode = 1;
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
if (!serveUi) open = false;
|
|
904
|
-
if (!enableIdentityProxy && serveUi) {
|
|
905
|
-
console.error(
|
|
906
|
-
"The decoupled UI is composed only by the protected ingress. Use --backend-only with --no-identity-proxy, or keep the identity proxy enabled.",
|
|
907
|
-
);
|
|
908
|
-
process.exitCode = 1;
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const uiOriginAllowlist = parseUiOriginAllowlist(process.env["AGENT_INSPECTOR_UI_ORIGINS"]);
|
|
912
|
-
if (uiOriginAllowlist.rejected.length > 0) {
|
|
913
|
-
console.error(
|
|
914
|
-
`Invalid AGENT_INSPECTOR_UI_ORIGINS entries: ${uiOriginAllowlist.rejected.join(", ")}. Only exact loopback http(s) origins are accepted; use a same-origin authenticated gateway for remote UI hosting.`,
|
|
915
|
-
);
|
|
916
|
-
process.exitCode = 1;
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
const publicOrigin = normalizeExplicitPublicOrigin(process.env["AGENT_INSPECTOR_PUBLIC_ORIGIN"]);
|
|
920
|
-
if (publicOrigin === null) {
|
|
921
|
-
console.error(
|
|
922
|
-
"Invalid AGENT_INSPECTOR_PUBLIC_ORIGIN. Use an exact http(s) origin without credentials, path, query, or fragment.",
|
|
923
|
-
);
|
|
924
|
-
process.exitCode = 1;
|
|
925
|
-
return;
|
|
926
|
-
}
|
|
927
|
-
const uiDirectoryCandidate = join(__dirname, "../.output/ui");
|
|
928
|
-
const uiIndexPath = join(uiDirectoryCandidate, "index.html");
|
|
929
|
-
const uiDirectory = serveUi && existsSync(uiIndexPath) ? uiDirectoryCandidate : undefined;
|
|
930
|
-
if (serveUi && uiDirectory === undefined) {
|
|
931
|
-
console.error(
|
|
932
|
-
`Agent Inspector UI artifact is missing at ${uiIndexPath}. Run the composed build or use --backend-only.`,
|
|
933
|
-
);
|
|
934
|
-
process.exitCode = 1;
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const outputDir = __dirname;
|
|
939
|
-
const serverPath = join(outputDir, "../.output/server/index.mjs");
|
|
940
|
-
const serverCommandResolution = resolveServerCommand(outputDir, serverPath, backend);
|
|
941
|
-
if (serverCommandResolution.kind === "error") {
|
|
942
|
-
console.error(serverCommandResolution.message);
|
|
943
|
-
process.exitCode = 1;
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
const serverCommand = serverCommandResolution.command;
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* Check if a port is in use and kill the process using it
|
|
950
|
-
*/
|
|
951
|
-
function killProcessOnPort(targetPort: number): void {
|
|
952
|
-
const pids = findPidsByPort(targetPort);
|
|
953
|
-
for (const pid of pids) {
|
|
954
|
-
console.log(`Killing process ${pid} on port ${targetPort}...`);
|
|
955
|
-
if (!killPid(pid)) {
|
|
956
|
-
console.error(`Failed to kill process ${pid} on port ${targetPort}.`);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
const legacyAliasPort =
|
|
962
|
-
enableIdentityProxy && legacyAliasEnabled && port === DEFAULT_PORT ? LEGACY_ALIAS_PORT : null;
|
|
963
|
-
|
|
964
|
-
// TanStack Start listens on the private upstream port; identity proxies own
|
|
965
|
-
// the public-facing port(s). When the identity proxy is disabled we fall
|
|
966
|
-
// back to TanStack listening on the public port directly.
|
|
967
|
-
const upstreamPort =
|
|
968
|
-
managedLaunch === null
|
|
969
|
-
? enableIdentityProxy
|
|
970
|
-
? pickUpstreamPort(port, legacyAliasPort)
|
|
971
|
-
: port
|
|
972
|
-
: managedLaunch.upstreamPort;
|
|
973
|
-
if (
|
|
974
|
-
upstreamPort < 1 ||
|
|
975
|
-
upstreamPort > 65_535 ||
|
|
976
|
-
(enableIdentityProxy && upstreamPort === port) ||
|
|
977
|
-
upstreamPort === legacyAliasPort
|
|
978
|
-
) {
|
|
979
|
-
console.error(`Invalid managed upstream port: ${String(upstreamPort)}.`);
|
|
980
|
-
process.exitCode = 1;
|
|
981
|
-
return;
|
|
982
|
-
}
|
|
983
|
-
const upstreamHost = enableIdentityProxy ? DEFAULT_BIND_HOST : host;
|
|
984
|
-
process.env["PORT"] = String(upstreamPort);
|
|
985
|
-
|
|
986
|
-
const configuredBasePath = process.env[AGENT_INSPECTOR_BASE_PATH_ENV];
|
|
987
|
-
if (configuredBasePath !== undefined && normalizePublicBasePath(configuredBasePath) === null) {
|
|
988
|
-
console.error(
|
|
989
|
-
`Invalid ${AGENT_INSPECTOR_BASE_PATH_ENV}. Use / for root or URL-safe path segments such as /inspector or /foo-bar/v1.`,
|
|
990
|
-
);
|
|
991
|
-
process.exitCode = 1;
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
const basePath = getConfiguredBasePath(process.env);
|
|
995
|
-
const bindOrigin = urlForHost(port, host);
|
|
996
|
-
const outwardOrigin = publicOrigin ?? bindOrigin;
|
|
997
|
-
const surfaceBaseUrl = appendBasePathToOrigin(outwardOrigin, "/", basePath);
|
|
998
|
-
const uiUrl = serveUi ? surfaceBaseUrl : null;
|
|
999
|
-
const proxyUrl = appendBasePathToOrigin(outwardOrigin, "/proxy", basePath);
|
|
1000
|
-
const openAiProxyUrl = appendBasePathToOrigin(outwardOrigin, "/proxy/v1", basePath);
|
|
1001
|
-
const proxyMessagesUrl = appendBasePathToOrigin(outwardOrigin, "/proxy/v1/messages", basePath);
|
|
1002
|
-
const legacyUrl =
|
|
1003
|
-
legacyAliasPort === null
|
|
1004
|
-
? null
|
|
1005
|
-
: appendBasePathToOrigin(localUrlForPort(legacyAliasPort), "/", basePath);
|
|
1006
|
-
const legacyProxyUrl =
|
|
1007
|
-
legacyAliasPort === null
|
|
1008
|
-
? null
|
|
1009
|
-
: appendBasePathToOrigin(localUrlForPort(legacyAliasPort), "/proxy", basePath);
|
|
1010
|
-
const legacyMcpUrl =
|
|
1011
|
-
legacyAliasPort === null
|
|
1012
|
-
? null
|
|
1013
|
-
: appendBasePathToOrigin(localUrlForPort(legacyAliasPort), "/api/mcp", basePath);
|
|
1014
|
-
|
|
1015
|
-
if (!forceRestart && (await isInspectorHealthy(port, host, basePath))) {
|
|
1016
|
-
const runningBackend = await getRunningBackend(port, host, basePath);
|
|
1017
|
-
if (runningBackend !== backend) {
|
|
1018
|
-
console.error(
|
|
1019
|
-
runningBackend === null
|
|
1020
|
-
? `A healthy Agent Inspector is already running at ${surfaceBaseUrl}, but its backend implementation cannot be verified. Use --force-restart to replace it safely.`
|
|
1021
|
-
: `Agent Inspector at ${surfaceBaseUrl} uses ${runningBackend}, but ${backend} was requested. Use --force-restart to switch implementations.`,
|
|
1022
|
-
);
|
|
1023
|
-
process.exitCode = 1;
|
|
1024
|
-
return;
|
|
1025
|
-
}
|
|
1026
|
-
console.log(
|
|
1027
|
-
`agent-inspector ${serveUi ? "is already running" : "backend is already running"} at ${surfaceBaseUrl}`,
|
|
1028
|
-
);
|
|
1029
|
-
printAccessHints(port, host, basePath, serveUi, publicOrigin);
|
|
1030
|
-
if (captureModeWasSpecified) {
|
|
1031
|
-
const runningMode = await getRunningCaptureMode(port, host, basePath);
|
|
1032
|
-
if (runningMode !== null && runningMode !== captureMode) {
|
|
1033
|
-
console.log(`Existing instance capture mode is ${runningMode}; requested ${captureMode}.`);
|
|
1034
|
-
console.log(`Use --force-restart to restart with ${captureMode} mode.`);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
console.log(`Use --force-restart to restart the existing instance.`);
|
|
1038
|
-
if (open && openWasSpecified && uiUrl !== null) {
|
|
1039
|
-
openBrowser(uiUrl);
|
|
1040
|
-
}
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
if (!forceRestart && (await isPortAcceptingConnections(port, host))) {
|
|
1045
|
-
console.error(`Port ${port} is already in use, but it is not a healthy agent-inspector.`);
|
|
1046
|
-
console.error(`Stop that process, choose --port <n>, or re-run with --force-restart.`);
|
|
1047
|
-
process.exitCode = 1;
|
|
1048
|
-
return;
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
if (forceRestart) {
|
|
1052
|
-
const restartPorts = new Set<number>();
|
|
1053
|
-
restartPorts.add(port);
|
|
1054
|
-
if (enableIdentityProxy) {
|
|
1055
|
-
restartPorts.add(upstreamPort);
|
|
1056
|
-
if (legacyAliasPort !== null) restartPorts.add(legacyAliasPort);
|
|
1057
|
-
}
|
|
1058
|
-
for (const targetPort of restartPorts) {
|
|
1059
|
-
killProcessOnPort(targetPort);
|
|
1060
|
-
}
|
|
1061
|
-
if (!(await waitForPortsToRelease(restartPorts, host))) {
|
|
1062
|
-
console.error("Agent Inspector could not restart because one or more ports are still busy.");
|
|
1063
|
-
process.exitCode = 1;
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
if (background && enableIdentityProxy) {
|
|
1069
|
-
const cliEntry = process.argv[1];
|
|
1070
|
-
if (cliEntry === undefined || cliEntry.length === 0) {
|
|
1071
|
-
console.error("Cannot resolve agent-inspector CLI entry for background supervisor.");
|
|
1072
|
-
process.exitCode = 1;
|
|
1073
|
-
return;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
const supervisorArgs = buildBackgroundSupervisorArgs({
|
|
1077
|
-
backend,
|
|
1078
|
-
port,
|
|
1079
|
-
host,
|
|
1080
|
-
configDir,
|
|
1081
|
-
providersJson,
|
|
1082
|
-
captureMode,
|
|
1083
|
-
enableIdentityProxy,
|
|
1084
|
-
legacyAliasEnabled,
|
|
1085
|
-
serveUi,
|
|
1086
|
-
});
|
|
1087
|
-
const supervisorProcess = spawn(process.execPath, [cliEntry, ...supervisorArgs], {
|
|
1088
|
-
stdio: "ignore",
|
|
1089
|
-
detached: true,
|
|
1090
|
-
env: {
|
|
1091
|
-
...process.env,
|
|
1092
|
-
[AGENT_INSPECTOR_CLI_ENTRY_ENV]: cliEntry,
|
|
1093
|
-
[AGENT_INSPECTOR_BACKGROUND_SUPERVISOR_ENV]: "1",
|
|
1094
|
-
},
|
|
1095
|
-
windowsHide: true,
|
|
1096
|
-
});
|
|
1097
|
-
supervisorProcess.once("error", (err) => {
|
|
1098
|
-
console.error(`Failed to start Agent Inspector background supervisor: ${err.message}`);
|
|
1099
|
-
});
|
|
1100
|
-
supervisorProcess.unref();
|
|
1101
|
-
|
|
1102
|
-
if (await waitForInspectorHealthy(port, startupReadyTimeoutMs(backend), host, basePath)) {
|
|
1103
|
-
console.log(
|
|
1104
|
-
`agent-inspector background ${serveUi ? "server" : "backend"} is ready at ${surfaceBaseUrl}`,
|
|
1105
|
-
);
|
|
1106
|
-
printAccessHints(port, host, basePath, serveUi, publicOrigin);
|
|
1107
|
-
if (legacyProxyUrl !== null && legacyMcpUrl !== null) {
|
|
1108
|
-
console.log(` Legacy proxy: ${legacyProxyUrl}`);
|
|
1109
|
-
console.log(` Legacy MCP: ${legacyMcpUrl}`);
|
|
1110
|
-
}
|
|
1111
|
-
console.log(``);
|
|
1112
|
-
console.log(`Route AI coding tools through the identity proxy for accurate PID attribution:`);
|
|
1113
|
-
console.log(` Claude Code: ANTHROPIC_BASE_URL=${proxyUrl} claude`);
|
|
1114
|
-
console.log(` OpenCode: LLM_BASE_URL=${proxyUrl} opencode`);
|
|
1115
|
-
console.log(` MiMo Code: OPENAI_BASE_URL=${openAiProxyUrl} mimo`);
|
|
1116
|
-
if (open && openWasSpecified && uiUrl !== null) {
|
|
1117
|
-
openBrowser(uiUrl);
|
|
1118
|
-
}
|
|
1119
|
-
return;
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
console.error(`agent-inspector background server did not become ready at ${surfaceBaseUrl}.`);
|
|
1123
|
-
process.exitCode = 1;
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// Start the server with the branded Windows runtime when postinstall created it.
|
|
1128
|
-
const serverEnv = { ...process.env };
|
|
1129
|
-
const currentCliEntry = process.env[AGENT_INSPECTOR_CLI_ENTRY_ENV] ?? process.argv[1];
|
|
1130
|
-
if (currentCliEntry !== undefined && currentCliEntry.length > 0) {
|
|
1131
|
-
serverEnv[AGENT_INSPECTOR_CLI_ENTRY_ENV] = currentCliEntry;
|
|
1132
|
-
}
|
|
1133
|
-
if (configDir !== undefined) {
|
|
1134
|
-
// Normalize MSYS / Git Bash paths to Windows native form.
|
|
1135
|
-
// On Windows, `path.join('/c/Users/foo')` becomes `\c\Users\foo`
|
|
1136
|
-
// (leading slash converted to backslash).
|
|
1137
|
-
// Child processes spawned by `spawn()` won't follow that style, so
|
|
1138
|
-
// rewrite `\c\...` (or any `\x\...` drive) to `C:\...` before
|
|
1139
|
-
// handing the path to the proxy server. No-op on already-native
|
|
1140
|
-
// Windows paths and on non-Windows platforms.
|
|
1141
|
-
let resolvedPath = join(configDir);
|
|
1142
|
-
const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
|
|
1143
|
-
if (msysMatch !== null) {
|
|
1144
|
-
const drive = (msysMatch[1] ?? "").toUpperCase();
|
|
1145
|
-
const rest = msysMatch[2] ?? "";
|
|
1146
|
-
resolvedPath = `${drive}:\\${rest}`;
|
|
1147
|
-
} else {
|
|
1148
|
-
resolvedPath = resolvePath(resolvedPath);
|
|
1149
|
-
}
|
|
1150
|
-
serverEnv["AGENT_INSPECTOR_CONFIG_DIR"] = resolvedPath;
|
|
1151
|
-
}
|
|
1152
|
-
if (providersJson !== undefined) {
|
|
1153
|
-
serverEnv["AGENT_INSPECTOR_PROVIDERS_JSON"] = providersJson;
|
|
1154
|
-
}
|
|
1155
|
-
serverEnv["HOST"] = upstreamHost;
|
|
1156
|
-
serverEnv["NITRO_HOST"] = upstreamHost;
|
|
1157
|
-
serverEnv["PORT"] = String(upstreamPort);
|
|
1158
|
-
serverEnv["NITRO_PORT"] = String(upstreamPort);
|
|
1159
|
-
if (enableIdentityProxy && basePath.length > 0) {
|
|
1160
|
-
serverEnv[AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH_ENV] = "1";
|
|
1161
|
-
}
|
|
1162
|
-
serverEnv["AGENT_INSPECTOR_PUBLIC_PORT"] = String(port);
|
|
1163
|
-
serverEnv["PROXY_PORT"] = String(upstreamPort);
|
|
1164
|
-
serverEnv["AGENT_INSPECTOR_CAPTURE_MODE"] = captureMode;
|
|
1165
|
-
serverEnv[AGENT_INSPECTOR_BACKEND_ENV] = backend;
|
|
1166
|
-
serverEnv["AGENT_INSPECTOR_BACKEND_ONLY"] = serveUi ? "0" : "1";
|
|
1167
|
-
const workerDir = join(__dirname, "workers");
|
|
1168
|
-
if (existsSync(workerDir)) {
|
|
1169
|
-
serverEnv["AGENT_INSPECTOR_WORKER_DIR"] = workerDir;
|
|
1170
|
-
}
|
|
1171
|
-
const runningInBackgroundSupervisor =
|
|
1172
|
-
process.env[AGENT_INSPECTOR_BACKGROUND_SUPERVISOR_ENV] === "1";
|
|
1173
|
-
const backgroundSupervisorLogs = runningInBackgroundSupervisor
|
|
1174
|
-
? createBackgroundSupervisorLogs(serverEnv)
|
|
1175
|
-
: null;
|
|
1176
|
-
const shouldPipeBackgroundRuntimeLogs = backgroundSupervisorLogs !== null;
|
|
1177
|
-
const serverProcess = spawn(serverCommand.command, serverCommand.args, {
|
|
1178
|
-
stdio:
|
|
1179
|
-
managedLaunch === null
|
|
1180
|
-
? background && !shouldPipeBackgroundRuntimeLogs
|
|
1181
|
-
? ["ignore", "ignore", "ignore"]
|
|
1182
|
-
: ["ignore", "pipe", "pipe"]
|
|
1183
|
-
: backend === "typescript"
|
|
1184
|
-
? background && !shouldPipeBackgroundRuntimeLogs
|
|
1185
|
-
? ["ignore", "ignore", "ignore", "ipc"]
|
|
1186
|
-
: ["ignore", "pipe", "pipe", "ipc"]
|
|
1187
|
-
: background
|
|
1188
|
-
? shouldPipeBackgroundRuntimeLogs
|
|
1189
|
-
? ["ignore", "pipe", "pipe"]
|
|
1190
|
-
: ["ignore", "ignore", "ignore"]
|
|
1191
|
-
: ["ignore", "pipe", "pipe"],
|
|
1192
|
-
detached: background,
|
|
1193
|
-
env: serverEnv,
|
|
1194
|
-
windowsHide: background || runningInBackgroundSupervisor,
|
|
1195
|
-
});
|
|
1196
|
-
serverProcess.once("error", (err) => {
|
|
1197
|
-
console.error(`Failed to start Agent Inspector runtime: ${err.message}`);
|
|
1198
|
-
});
|
|
1199
|
-
const serverExit = waitForProcessExit(serverProcess);
|
|
1200
|
-
if (!background || backgroundSupervisorLogs !== null) {
|
|
1201
|
-
const outputContext = {
|
|
1202
|
-
enableIdentityProxy,
|
|
1203
|
-
publicPort: port,
|
|
1204
|
-
upstreamPort,
|
|
1205
|
-
};
|
|
1206
|
-
if (backgroundSupervisorLogs === null) {
|
|
1207
|
-
pipeServerOutput(serverProcess, outputContext);
|
|
1208
|
-
} else {
|
|
1209
|
-
pipeServerOutputStreamToFile(
|
|
1210
|
-
serverProcess.stdout,
|
|
1211
|
-
backgroundSupervisorLogs.stdoutPath,
|
|
1212
|
-
outputContext,
|
|
1213
|
-
);
|
|
1214
|
-
pipeServerOutputStreamToFile(
|
|
1215
|
-
serverProcess.stderr,
|
|
1216
|
-
backgroundSupervisorLogs.stderrPath,
|
|
1217
|
-
outputContext,
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// Identity proxy — a native Node HTTP server that sits in front of
|
|
1223
|
-
// TanStack Start and looks up each client's PID via the OS connection
|
|
1224
|
-
// table, injecting the result as `X-Agent-Inspector-Client-*` headers.
|
|
1225
|
-
// This lets AI tools (which can't easily set those headers themselves)
|
|
1226
|
-
// still get accurate session attribution. Started only after TanStack
|
|
1227
|
-
// Start is healthy, so the upstream is ready by the time clients connect.
|
|
1228
|
-
const identityProxies: Array<{
|
|
1229
|
-
label: "primary" | "legacy";
|
|
1230
|
-
port: number;
|
|
1231
|
-
server: import("node:http").Server;
|
|
1232
|
-
}> = [];
|
|
1233
|
-
let supervisorShutdownPromise: Promise<void> | null = null;
|
|
1234
|
-
const requestSupervisorShutdown = (signal: "SIGINT" | "SIGTERM"): void => {
|
|
1235
|
-
if (supervisorShutdownPromise !== null) return;
|
|
1236
|
-
for (const proxy of identityProxies) stopIdentityProxyAdmission(proxy.server);
|
|
1237
|
-
if (managedLaunch !== null && backend === "typescript" && serverProcess.connected) {
|
|
1238
|
-
serverProcess.send(managedInstanceShutdownMessage(managedLaunch.controlToken), (error) => {
|
|
1239
|
-
if (error !== null && serverProcess.exitCode === null) serverProcess.kill(signal);
|
|
1240
|
-
});
|
|
1241
|
-
} else {
|
|
1242
|
-
serverProcess.kill(signal);
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
const childDeadline =
|
|
1246
|
-
readPositiveIntegerEnv("AGENT_INSPECTOR_SHUTDOWN_TIMEOUT_MS") ?? DEFAULT_SHUTDOWN_DEADLINE_MS;
|
|
1247
|
-
let deadline: ReturnType<typeof setTimeout> | null = null;
|
|
1248
|
-
let forced = false;
|
|
1249
|
-
const graceful = Promise.all([
|
|
1250
|
-
serverExit.then(() => undefined),
|
|
1251
|
-
...identityProxies.map(async (proxy) => await closeIdentityProxyGracefully(proxy.server)),
|
|
1252
|
-
]).then(() => undefined);
|
|
1253
|
-
const timedOut = new Promise<void>((resolve) => {
|
|
1254
|
-
deadline = setTimeout(() => {
|
|
1255
|
-
forced = true;
|
|
1256
|
-
for (const proxy of identityProxies) forceCloseIdentityProxy(proxy.server);
|
|
1257
|
-
if (serverProcess.exitCode === null) serverProcess.kill("SIGKILL");
|
|
1258
|
-
resolve();
|
|
1259
|
-
}, childDeadline + SUPERVISOR_SHUTDOWN_GRACE_MS);
|
|
1260
|
-
});
|
|
1261
|
-
supervisorShutdownPromise = Promise.race([graceful, timedOut])
|
|
1262
|
-
.finally(() => {
|
|
1263
|
-
if (deadline !== null) clearTimeout(deadline);
|
|
1264
|
-
})
|
|
1265
|
-
.then(() => {
|
|
1266
|
-
if (forced) process.exitCode = 1;
|
|
1267
|
-
});
|
|
1268
|
-
};
|
|
1269
|
-
const handleSigint = (): void => requestSupervisorShutdown("SIGINT");
|
|
1270
|
-
const handleSigterm = (): void => requestSupervisorShutdown("SIGTERM");
|
|
1271
|
-
const currentSupervisorShutdown = (): Promise<void> | null => supervisorShutdownPromise;
|
|
1272
|
-
process.on("SIGINT", handleSigint);
|
|
1273
|
-
process.on("SIGTERM", handleSigterm);
|
|
1274
|
-
const managedInstanceControl: ManagedInstanceControlOptions | undefined =
|
|
1275
|
-
managedLaunch === null
|
|
1276
|
-
? undefined
|
|
1277
|
-
: {
|
|
1278
|
-
name: managedLaunch.name,
|
|
1279
|
-
instanceId: managedLaunch.instanceId,
|
|
1280
|
-
launchId: managedLaunch.launchId,
|
|
1281
|
-
controlToken: managedLaunch.controlToken,
|
|
1282
|
-
supervisorPid: process.pid,
|
|
1283
|
-
startedAt: managedLaunch.startedAt,
|
|
1284
|
-
backend,
|
|
1285
|
-
publicPort: port,
|
|
1286
|
-
upstreamPort,
|
|
1287
|
-
requestShutdown: () => requestSupervisorShutdown("SIGTERM"),
|
|
1288
|
-
};
|
|
1289
|
-
if (enableIdentityProxy) {
|
|
1290
|
-
const primaryProxy = await tryStartIdentityProxy(
|
|
1291
|
-
port,
|
|
1292
|
-
upstreamPort,
|
|
1293
|
-
host,
|
|
1294
|
-
upstreamHost,
|
|
1295
|
-
managedInstanceControl,
|
|
1296
|
-
uiOriginAllowlist.origins,
|
|
1297
|
-
uiDirectory,
|
|
1298
|
-
publicOrigin,
|
|
1299
|
-
backend,
|
|
1300
|
-
);
|
|
1301
|
-
if (primaryProxy === null) {
|
|
1302
|
-
console.error(
|
|
1303
|
-
`Agent Inspector could not bind its protected public ingress at ${bindOrigin}; stopping the private backend.`,
|
|
1304
|
-
);
|
|
1305
|
-
requestSupervisorShutdown("SIGTERM");
|
|
1306
|
-
const failedStartupShutdown = currentSupervisorShutdown();
|
|
1307
|
-
if (failedStartupShutdown !== null) await failedStartupShutdown;
|
|
1308
|
-
process.off("SIGINT", handleSigint);
|
|
1309
|
-
process.off("SIGTERM", handleSigterm);
|
|
1310
|
-
process.exitCode = 1;
|
|
1311
|
-
return;
|
|
1312
|
-
}
|
|
1313
|
-
identityProxies.push({ label: "primary", port, server: primaryProxy });
|
|
1314
|
-
if (legacyAliasPort !== null) {
|
|
1315
|
-
const legacyProxy = await tryStartIdentityProxy(
|
|
1316
|
-
legacyAliasPort,
|
|
1317
|
-
upstreamPort,
|
|
1318
|
-
host,
|
|
1319
|
-
upstreamHost,
|
|
1320
|
-
managedInstanceControl,
|
|
1321
|
-
uiOriginAllowlist.origins,
|
|
1322
|
-
uiDirectory,
|
|
1323
|
-
publicOrigin,
|
|
1324
|
-
backend,
|
|
1325
|
-
);
|
|
1326
|
-
if (legacyProxy !== null) {
|
|
1327
|
-
identityProxies.push({ label: "legacy", port: legacyAliasPort, server: legacyProxy });
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
if (background) {
|
|
1333
|
-
serverProcess.unref();
|
|
1334
|
-
// Probe the upstream TanStack port directly — the identity proxy is
|
|
1335
|
-
// not started yet so the public port is not yet accepting traffic.
|
|
1336
|
-
const upstreamProbeBasePath = enableIdentityProxy ? "" : basePath;
|
|
1337
|
-
if (
|
|
1338
|
-
await waitForInspectorHealthy(
|
|
1339
|
-
upstreamPort,
|
|
1340
|
-
startupReadyTimeoutMs(backend),
|
|
1341
|
-
host,
|
|
1342
|
-
upstreamProbeBasePath,
|
|
1343
|
-
)
|
|
1344
|
-
) {
|
|
1345
|
-
console.log(
|
|
1346
|
-
`agent-inspector background ${serveUi ? "server" : "backend"} is ready at ${surfaceBaseUrl}`,
|
|
1347
|
-
);
|
|
1348
|
-
if (identityProxies.length > 0) {
|
|
1349
|
-
console.log(`Identity proxy ready on port ${port} (PID-attributing proxy).`);
|
|
1350
|
-
}
|
|
1351
|
-
printAccessHints(port, host, basePath, serveUi, publicOrigin);
|
|
1352
|
-
if (legacyProxyUrl !== null && legacyMcpUrl !== null) {
|
|
1353
|
-
console.log(` Legacy proxy: ${legacyProxyUrl}`);
|
|
1354
|
-
console.log(` Legacy MCP: ${legacyMcpUrl}`);
|
|
1355
|
-
}
|
|
1356
|
-
if (identityProxies.length > 0) {
|
|
1357
|
-
console.log(
|
|
1358
|
-
``,
|
|
1359
|
-
`Route AI coding tools through the identity proxy for accurate PID attribution:`,
|
|
1360
|
-
` Claude Code: ANTHROPIC_BASE_URL=${proxyUrl} claude`,
|
|
1361
|
-
` OpenCode: LLM_BASE_URL=${proxyUrl} opencode`,
|
|
1362
|
-
` MiMo Code: OPENAI_BASE_URL=${openAiProxyUrl} mimo`,
|
|
1363
|
-
legacyProxyUrl === null
|
|
1364
|
-
? ` Override the port with --port <n> when needed.`
|
|
1365
|
-
: ` Legacy configs using ${legacyProxyUrl} still work.`,
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
console.error(`agent-inspector background server did not become ready at ${surfaceBaseUrl}.`);
|
|
1371
|
-
process.exitCode = 1;
|
|
1372
|
-
return;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
if (!(await waitForInspectorHealthy(port, startupReadyTimeoutMs(backend), host, basePath))) {
|
|
1376
|
-
console.error(`Agent Inspector did not become ready at ${surfaceBaseUrl}; stopping startup.`);
|
|
1377
|
-
requestSupervisorShutdown("SIGTERM");
|
|
1378
|
-
const failedStartupShutdown = currentSupervisorShutdown();
|
|
1379
|
-
if (failedStartupShutdown !== null) await failedStartupShutdown;
|
|
1380
|
-
process.off("SIGINT", handleSigint);
|
|
1381
|
-
process.off("SIGTERM", handleSigterm);
|
|
1382
|
-
process.exitCode = 1;
|
|
1383
|
-
return;
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
if (identityProxies.length > 0) {
|
|
1387
|
-
console.log(``);
|
|
1388
|
-
console.log(`Identity proxy ready at ${bindOrigin} (PID-attributing ingress).`);
|
|
1389
|
-
if (legacyAliasPort !== null) {
|
|
1390
|
-
const legacyReady = identityProxies.some((proxy) => proxy.label === "legacy");
|
|
1391
|
-
if (legacyReady) {
|
|
1392
|
-
console.log(` Legacy alias ready at http://localhost:${legacyAliasPort}.`);
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
if (upstreamPort !== port) {
|
|
1396
|
-
console.log(` Internal runtime port ${upstreamPort} is hidden behind the public proxy.`);
|
|
1397
|
-
}
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
console.log(
|
|
1401
|
-
`${serveUi ? "UI and backend" : "Backend control and agent runtime"} running at ${surfaceBaseUrl}`,
|
|
1402
|
-
);
|
|
1403
|
-
console.log(` Capture mode: ${captureMode}`);
|
|
1404
|
-
console.log(` Backend: ${backend}`);
|
|
1405
|
-
console.log(` Proxy: ${proxyUrl}`);
|
|
1406
|
-
printAccessHints(port, host, basePath, serveUi, publicOrigin);
|
|
1407
|
-
if (legacyUrl !== null && legacyProxyUrl !== null && legacyMcpUrl !== null) {
|
|
1408
|
-
console.log(` Legacy UI: ${legacyUrl}`);
|
|
1409
|
-
console.log(` Legacy proxy: ${legacyProxyUrl}`);
|
|
1410
|
-
console.log(` Legacy MCP: ${legacyMcpUrl}`);
|
|
1411
|
-
}
|
|
1412
|
-
console.log(``);
|
|
1413
|
-
console.log(`Route AI coding tools through the proxy:`);
|
|
1414
|
-
console.log(` Claude Code: ANTHROPIC_BASE_URL=${proxyUrl} claude`);
|
|
1415
|
-
console.log(` OpenCode: LLM_BASE_URL=${proxyUrl} opencode`);
|
|
1416
|
-
console.log(` MiMo Code: OPENAI_BASE_URL=${openAiProxyUrl} mimo`);
|
|
1417
|
-
console.log(` Direct HTTP: curl ${proxyMessagesUrl} -d '{"model":"...","messages":[...]}'`);
|
|
1418
|
-
console.log(
|
|
1419
|
-
` Remote/container tools: use the Network proxy URL above when shown; otherwise restart with --host 0.0.0.0 or a reachable IP.`,
|
|
1420
|
-
);
|
|
1421
|
-
if (open && uiUrl !== null) openBrowser(uiUrl);
|
|
1422
|
-
|
|
1423
|
-
process.exitCode = await serverExit;
|
|
1424
|
-
|
|
1425
|
-
const activeSupervisorShutdown = currentSupervisorShutdown();
|
|
1426
|
-
if (activeSupervisorShutdown !== null) {
|
|
1427
|
-
await activeSupervisorShutdown;
|
|
1428
|
-
} else {
|
|
1429
|
-
await Promise.all(identityProxies.map((proxy) => closeIdentityProxyGracefully(proxy.server)));
|
|
1430
|
-
}
|
|
1431
|
-
process.off("SIGINT", handleSigint);
|
|
1432
|
-
process.off("SIGTERM", handleSigterm);
|
|
1433
|
-
}
|