@tonyclaw/agent-inspector 3.1.32 → 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.
Files changed (373) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +241 -120
  3. package/.output/server/_ssr/index.mjs +1 -1
  4. package/.output/server/_ssr/{router-oXUk3MBR.mjs → router-BBY_zWRK.mjs} +91 -3
  5. package/.output/server/index.mjs +1 -1
  6. package/.output/ui/assets/{CompareDrawer-BtfPSpkV.js → CompareDrawer-rtNElN-B.js} +1 -1
  7. package/.output/ui/assets/{InspectorPet-D9DPPQ3n.js → InspectorPet-QaAiuF3d.js} +1 -1
  8. package/.output/ui/assets/{ProxyViewerContainer-Buu8vkCK.js → ProxyViewerContainer-6twNZHwl.js} +4 -4
  9. package/.output/ui/assets/{ReplayDialog-BER-QzpD.js → ReplayDialog-B7KlziO2.js} +1 -1
  10. package/.output/ui/assets/{RequestAnatomy-C8Zwxrr7.js → RequestAnatomy-2XVFBrW4.js} +1 -1
  11. package/.output/ui/assets/{ResponseView-DDpKdYk6.js → ResponseView-BULgzLVB.js} +1 -1
  12. package/.output/ui/assets/{StreamingChunkSequence-sMPGG7Sw.js → StreamingChunkSequence-DSkvkbns.js} +1 -1
  13. package/.output/ui/assets/{_sessionId-e4IheHnP.js → _sessionId-D2dmJi3G.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-C5Uv72Fx.js → _sessionId-DcS0s1Mr.js} +1 -1
  15. package/.output/ui/assets/{index-B__qT0Yd.js → index-B0TN1KKO.js} +2 -2
  16. package/.output/ui/assets/{index-Be3bTCzi.js → index-Bva1zHEs.js} +1 -1
  17. package/.output/ui/assets/{index-C9q__p8J.js → index-BzBExOxu.js} +1 -1
  18. package/.output/ui/assets/{index-CtB0deSZ.js → index-k6sRQz55.js} +1 -1
  19. package/.output/ui/assets/{json-viewer-Db0AFp-7.js → json-viewer-CWM4tY-j.js} +1 -1
  20. package/.output/ui/assets/{jszip.min-DkY0Ym_q.js → jszip.min-BkWvRFlo.js} +1 -1
  21. package/.output/ui/index.html +1 -1
  22. package/package.json +8 -13
  23. package/src/assets/IDE/claudecode-color.svg +0 -1
  24. package/src/assets/IDE/codex-color.svg +0 -1
  25. package/src/assets/IDE/opencode.svg +0 -1
  26. package/src/assets/IDE/xiaomimimo.svg +0 -1
  27. package/src/assets/favicon.svg +0 -21
  28. package/src/assets/logos/alibaba.svg +0 -1
  29. package/src/assets/logos/anthropic.svg +0 -1
  30. package/src/assets/logos/claude-code.svg +0 -4
  31. package/src/assets/logos/deepseek.svg +0 -1
  32. package/src/assets/logos/minimax.jpeg +0 -0
  33. package/src/assets/logos/openai.svg +0 -1
  34. package/src/assets/logos/opencode.svg +0 -4
  35. package/src/assets/logos/qwen.webp +0 -0
  36. package/src/assets/logos/zhipuai.svg +0 -219
  37. package/src/backend/router.tsx +0 -36
  38. package/src/backend/routes/$.ts +0 -26
  39. package/src/backend/routes/__root.ts +0 -4
  40. package/src/backend/routes/api/-instances.ts +0 -7
  41. package/src/backend/routes/api/alerts.summary.ts +0 -28
  42. package/src/backend/routes/api/alerts.ts +0 -70
  43. package/src/backend/routes/api/config.paths.ts +0 -14
  44. package/src/backend/routes/api/config.ts +0 -73
  45. package/src/backend/routes/api/ecosystem.packages.$packageId.help.ts +0 -11
  46. package/src/backend/routes/api/ecosystem.packages.$packageId.install.ts +0 -11
  47. package/src/backend/routes/api/ecosystem.packages.$packageId.runner-presets.ts +0 -11
  48. package/src/backend/routes/api/ecosystem.packages.$packageId.upgrade.ts +0 -11
  49. package/src/backend/routes/api/ecosystem.packages.ts +0 -21
  50. package/src/backend/routes/api/ecosystem.recipes.$recipeId.run.ts +0 -11
  51. package/src/backend/routes/api/ecosystem.tasks.$taskId.ts +0 -16
  52. package/src/backend/routes/api/ecosystem.tasks.ts +0 -12
  53. package/src/backend/routes/api/groups.$groupId.evidence.ts +0 -55
  54. package/src/backend/routes/api/groups.$groupId.sessions.ts +0 -50
  55. package/src/backend/routes/api/groups.$groupId.ts +0 -93
  56. package/src/backend/routes/api/groups.ts +0 -79
  57. package/src/backend/routes/api/health.ts +0 -22
  58. package/src/backend/routes/api/instances.$name.connection.ts +0 -12
  59. package/src/backend/routes/api/instances.$name.restart.ts +0 -23
  60. package/src/backend/routes/api/instances.$name.start.ts +0 -18
  61. package/src/backend/routes/api/instances.$name.stop.ts +0 -23
  62. package/src/backend/routes/api/instances.$name.ts +0 -12
  63. package/src/backend/routes/api/instances.ts +0 -16
  64. package/src/backend/routes/api/knowledge.candidates.$candidateId.promote.ts +0 -32
  65. package/src/backend/routes/api/knowledge.candidates.$candidateId.ts +0 -50
  66. package/src/backend/routes/api/knowledge.candidates.ts +0 -10
  67. package/src/backend/routes/api/knowledge.project-context.ts +0 -29
  68. package/src/backend/routes/api/knowledge.search.ts +0 -62
  69. package/src/backend/routes/api/knowledge.sessions.$sessionId.candidates.ts +0 -26
  70. package/src/backend/routes/api/logs.$id.body.ts +0 -58
  71. package/src/backend/routes/api/logs.$id.chunks.ts +0 -37
  72. package/src/backend/routes/api/logs.$id.replay.ts +0 -297
  73. package/src/backend/routes/api/logs.$id.ts +0 -23
  74. package/src/backend/routes/api/logs.import.ts +0 -39
  75. package/src/backend/routes/api/logs.stream.ts +0 -174
  76. package/src/backend/routes/api/logs.ts +0 -214
  77. package/src/backend/routes/api/mcp.ts +0 -25
  78. package/src/backend/routes/api/models.ts +0 -10
  79. package/src/backend/routes/api/pi-agent.ts +0 -29
  80. package/src/backend/routes/api/providers.$providerId.model-metadata.ts +0 -173
  81. package/src/backend/routes/api/providers.$providerId.test.log.ts +0 -342
  82. package/src/backend/routes/api/providers.$providerId.ts +0 -111
  83. package/src/backend/routes/api/providers.export.ts +0 -44
  84. package/src/backend/routes/api/providers.import.ts +0 -81
  85. package/src/backend/routes/api/providers.scan.ts +0 -28
  86. package/src/backend/routes/api/providers.ts +0 -102
  87. package/src/backend/routes/api/runs.$runId.evidence.ts +0 -55
  88. package/src/backend/routes/api/runs.$runId.ts +0 -50
  89. package/src/backend/routes/api/runs.ts +0 -62
  90. package/src/backend/routes/api/runtime.ts +0 -46
  91. package/src/backend/routes/api/sessions.ts +0 -51
  92. package/src/backend/routes/api/storage.ts +0 -56
  93. package/src/backend/routes/livez.ts +0 -13
  94. package/src/backend/routes/metrics.ts +0 -14
  95. package/src/backend/routes/proxy/$.ts +0 -15
  96. package/src/backend/routes/readyz.ts +0 -18
  97. package/src/backend/start.ts +0 -10
  98. package/src/cli/alias.ts +0 -431
  99. package/src/cli/detect-tools.ts +0 -123
  100. package/src/cli/doctor.ts +0 -836
  101. package/src/cli/instance.ts +0 -1071
  102. package/src/cli/instanceApi.ts +0 -213
  103. package/src/cli/instanceArgs.ts +0 -301
  104. package/src/cli/instanceControl.ts +0 -191
  105. package/src/cli/instanceModel.ts +0 -276
  106. package/src/cli/instanceRegistry.ts +0 -731
  107. package/src/cli/networkHints.ts +0 -170
  108. package/src/cli/onboard.ts +0 -1668
  109. package/src/cli/onboardTarget.ts +0 -228
  110. package/src/cli/rustBackendPackage.ts +0 -458
  111. package/src/cli/startupOutput.ts +0 -16
  112. package/src/cli/templates/codex-skill-onboard.ts +0 -320
  113. package/src/cli/templates/command-onboard.ts +0 -25
  114. package/src/cli/templates/skill-onboard.ts +0 -625
  115. package/src/cli.ts +0 -1397
  116. package/src/components/OnboardingBanner.tsx +0 -115
  117. package/src/components/ProxyViewer.tsx +0 -2259
  118. package/src/components/ProxyViewerContainer.tsx +0 -1076
  119. package/src/components/alerts/AlertsDialog.tsx +0 -615
  120. package/src/components/clients/ClientLogo.tsx +0 -175
  121. package/src/components/ecosystem/AgentLabDialog.tsx +0 -947
  122. package/src/components/errors/SafeErrorBoundary.tsx +0 -201
  123. package/src/components/groups/GroupsDialog.tsx +0 -1304
  124. package/src/components/inspector-pet/InspectorPet.tsx +0 -398
  125. package/src/components/inspector-pet/InspectorPetCrab3D.tsx +0 -455
  126. package/src/components/inspector-pet/inspectorPetLogic.ts +0 -272
  127. package/src/components/pi-agent/PiAgentPanel.tsx +0 -446
  128. package/src/components/pi-agent/piAgentChatLogic.ts +0 -273
  129. package/src/components/providers/ImportWizardDialog.tsx +0 -281
  130. package/src/components/providers/ProviderCard.tsx +0 -347
  131. package/src/components/providers/ProviderForm.tsx +0 -1183
  132. package/src/components/providers/ProviderLogo.tsx +0 -122
  133. package/src/components/providers/ProviderTestResultsView.tsx +0 -734
  134. package/src/components/providers/ProvidersPanel.tsx +0 -714
  135. package/src/components/providers/SettingsDialog.tsx +0 -1042
  136. package/src/components/providers/providerFormCapabilities.ts +0 -36
  137. package/src/components/proxy-viewer/AgentTraceSummary.tsx +0 -902
  138. package/src/components/proxy-viewer/AnswerMarkdown.tsx +0 -51
  139. package/src/components/proxy-viewer/ApplicationBar.tsx +0 -123
  140. package/src/components/proxy-viewer/CompareDrawer.tsx +0 -984
  141. package/src/components/proxy-viewer/ConversationGroup.tsx +0 -208
  142. package/src/components/proxy-viewer/ConversationGroupList.tsx +0 -652
  143. package/src/components/proxy-viewer/ConversationHeader.tsx +0 -429
  144. package/src/components/proxy-viewer/LiveConnectionStatus.tsx +0 -186
  145. package/src/components/proxy-viewer/LogEntry.tsx +0 -1419
  146. package/src/components/proxy-viewer/LogEntryHeader.tsx +0 -596
  147. package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -57
  148. package/src/components/proxy-viewer/RawExportWarning.tsx +0 -77
  149. package/src/components/proxy-viewer/ReplayDialog.tsx +0 -636
  150. package/src/components/proxy-viewer/RequestToolsPanel.tsx +0 -299
  151. package/src/components/proxy-viewer/ResponseView.tsx +0 -202
  152. package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +0 -61
  153. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +0 -265
  154. package/src/components/proxy-viewer/ThreadConnector.tsx +0 -165
  155. package/src/components/proxy-viewer/ToolTraceEvents.tsx +0 -64
  156. package/src/components/proxy-viewer/TurnGroup.tsx +0 -383
  157. package/src/components/proxy-viewer/TurnGroupList.tsx +0 -156
  158. package/src/components/proxy-viewer/accessibility.ts +0 -8
  159. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +0 -887
  160. package/src/components/proxy-viewer/anatomy/SegmentBar.tsx +0 -222
  161. package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +0 -737
  162. package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +0 -268
  163. package/src/components/proxy-viewer/anatomy/tokenEstimate.ts +0 -53
  164. package/src/components/proxy-viewer/anatomy/types.ts +0 -41
  165. package/src/components/proxy-viewer/anatomy/useAnatomyJump.ts +0 -114
  166. package/src/components/proxy-viewer/bodyHydration.ts +0 -123
  167. package/src/components/proxy-viewer/cacheTrend.ts +0 -50
  168. package/src/components/proxy-viewer/diff/DiffView.tsx +0 -321
  169. package/src/components/proxy-viewer/diff/computeDiff.ts +0 -178
  170. package/src/components/proxy-viewer/diff/index.ts +0 -3
  171. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +0 -194
  172. package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +0 -81
  173. package/src/components/proxy-viewer/formats/anthropic/thinkingExtract.ts +0 -21
  174. package/src/components/proxy-viewer/formats/index.tsx +0 -47
  175. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +0 -442
  176. package/src/components/proxy-viewer/index.ts +0 -9
  177. package/src/components/proxy-viewer/lazy.ts +0 -41
  178. package/src/components/proxy-viewer/liveConnectionState.ts +0 -158
  179. package/src/components/proxy-viewer/log-formats/anthropic.ts +0 -191
  180. package/src/components/proxy-viewer/log-formats/index.ts +0 -23
  181. package/src/components/proxy-viewer/log-formats/openai.ts +0 -256
  182. package/src/components/proxy-viewer/log-formats/types.ts +0 -40
  183. package/src/components/proxy-viewer/log-formats/unknown.ts +0 -18
  184. package/src/components/proxy-viewer/logEntryVisibility.ts +0 -73
  185. package/src/components/proxy-viewer/logFocus.ts +0 -54
  186. package/src/components/proxy-viewer/proxyViewerContainerLogic.ts +0 -124
  187. package/src/components/proxy-viewer/proxyViewerLogic.ts +0 -488
  188. package/src/components/proxy-viewer/replayComparison.ts +0 -131
  189. package/src/components/proxy-viewer/requestDiff.ts +0 -277
  190. package/src/components/proxy-viewer/requestTools.ts +0 -220
  191. package/src/components/proxy-viewer/useCopyFeedback.ts +0 -38
  192. package/src/components/proxy-viewer/useKeyboardNavigation.ts +0 -232
  193. package/src/components/proxy-viewer/viewerState.ts +0 -176
  194. package/src/components/ui/badge.tsx +0 -47
  195. package/src/components/ui/button.tsx +0 -47
  196. package/src/components/ui/collapsible.tsx +0 -21
  197. package/src/components/ui/confirm-dialog.tsx +0 -51
  198. package/src/components/ui/crab-logo.tsx +0 -45
  199. package/src/components/ui/crab-variants.tsx +0 -467
  200. package/src/components/ui/dialog.tsx +0 -130
  201. package/src/components/ui/icon-trigger.ts +0 -2
  202. package/src/components/ui/json-viewer-bulk.ts +0 -132
  203. package/src/components/ui/json-viewer.tsx +0 -508
  204. package/src/components/ui/scroll-area.tsx +0 -54
  205. package/src/components/ui/select.tsx +0 -178
  206. package/src/components/ui/separator.tsx +0 -28
  207. package/src/components/ui/tabs.tsx +0 -88
  208. package/src/components/ui/tooltip.tsx +0 -51
  209. package/src/components/ui/transient-toast.tsx +0 -105
  210. package/src/contracts/anthropic.ts +0 -230
  211. package/src/contracts/index.ts +0 -59
  212. package/src/contracts/json.ts +0 -37
  213. package/src/contracts/log.ts +0 -139
  214. package/src/contracts/openai.ts +0 -267
  215. package/src/index.css +0 -11
  216. package/src/knowledge/candidateStore.ts +0 -94
  217. package/src/knowledge/distiller.ts +0 -98
  218. package/src/knowledge/openclawClient.ts +0 -161
  219. package/src/knowledge/openclawGatewayClient.ts +0 -249
  220. package/src/knowledge/openclawMarkdown.ts +0 -146
  221. package/src/knowledge/redactor.ts +0 -80
  222. package/src/knowledge/types.ts +0 -83
  223. package/src/lib/alertContract.ts +0 -76
  224. package/src/lib/apiClient.ts +0 -166
  225. package/src/lib/apiFormat.ts +0 -29
  226. package/src/lib/backendImplementation.ts +0 -16
  227. package/src/lib/basePath.ts +0 -107
  228. package/src/lib/browserRuntimeContract.ts +0 -124
  229. package/src/lib/browserRuntimeResolver.ts +0 -333
  230. package/src/lib/chunkLoadRecovery.ts +0 -83
  231. package/src/lib/claudeCodeStrip.ts +0 -98
  232. package/src/lib/clipboard.ts +0 -67
  233. package/src/lib/codingAgentEndpoints.ts +0 -54
  234. package/src/lib/ecosystemContract.ts +0 -130
  235. package/src/lib/export-logs.ts +0 -287
  236. package/src/lib/groupContract.ts +0 -166
  237. package/src/lib/instanceContract.ts +0 -239
  238. package/src/lib/jsonFile.ts +0 -46
  239. package/src/lib/logImportContract.ts +0 -12
  240. package/src/lib/managedInstance.ts +0 -112
  241. package/src/lib/mask.ts +0 -4
  242. package/src/lib/objectUtils.ts +0 -22
  243. package/src/lib/piAgentContract.ts +0 -102
  244. package/src/lib/providerContract.ts +0 -125
  245. package/src/lib/providerImportContract.ts +0 -27
  246. package/src/lib/providerModelMetadata.ts +0 -458
  247. package/src/lib/providerTestContract.ts +0 -170
  248. package/src/lib/providerTestPrompt.ts +0 -100
  249. package/src/lib/publicBasePathContext.ts +0 -137
  250. package/src/lib/resourceLimits.ts +0 -345
  251. package/src/lib/runContract.ts +0 -164
  252. package/src/lib/runtimeConfig.ts +0 -43
  253. package/src/lib/safeDiagnostic.ts +0 -38
  254. package/src/lib/safeFetch.ts +0 -508
  255. package/src/lib/serverPort.ts +0 -47
  256. package/src/lib/sessionInfoContract.ts +0 -78
  257. package/src/lib/sessionUrl.ts +0 -46
  258. package/src/lib/ssrfGuard.ts +0 -133
  259. package/src/lib/stopReason.ts +0 -130
  260. package/src/lib/themeMode.ts +0 -41
  261. package/src/lib/timeDisplay.ts +0 -22
  262. package/src/lib/toolTrace.ts +0 -290
  263. package/src/lib/uiScale.ts +0 -117
  264. package/src/lib/unknown.ts +0 -27
  265. package/src/lib/upstreamUrl.ts +0 -150
  266. package/src/lib/useAlerts.ts +0 -82
  267. package/src/lib/useGroupEvidence.ts +0 -37
  268. package/src/lib/useGroups.ts +0 -32
  269. package/src/lib/useOnboarding.ts +0 -93
  270. package/src/lib/useProviders.ts +0 -30
  271. package/src/lib/useStripConfig.ts +0 -154
  272. package/src/lib/useThemeMode.ts +0 -40
  273. package/src/lib/useUiScale.ts +0 -52
  274. package/src/lib/utils.ts +0 -78
  275. package/src/mcp/currentContext.ts +0 -65
  276. package/src/mcp/instanceHandlers.ts +0 -151
  277. package/src/mcp/loopback.ts +0 -88
  278. package/src/mcp/mode.ts +0 -75
  279. package/src/mcp/previewExtractor.ts +0 -233
  280. package/src/mcp/server.ts +0 -2274
  281. package/src/mcp/toolHandlers.ts +0 -1533
  282. package/src/proxy/alerts.ts +0 -664
  283. package/src/proxy/chunkStorage.ts +0 -123
  284. package/src/proxy/config.ts +0 -218
  285. package/src/proxy/constants.ts +0 -66
  286. package/src/proxy/dataDir.ts +0 -147
  287. package/src/proxy/ecosystemExecutionConfirmation.ts +0 -114
  288. package/src/proxy/ecosystemExecutionRoute.ts +0 -116
  289. package/src/proxy/ecosystemRegistry.ts +0 -296
  290. package/src/proxy/ecosystemTasks.ts +0 -415
  291. package/src/proxy/evidenceAnalysis.ts +0 -526
  292. package/src/proxy/evidenceExporter.ts +0 -235
  293. package/src/proxy/formats/anthropic/anthropicProvider.ts +0 -75
  294. package/src/proxy/formats/anthropic/handler.ts +0 -75
  295. package/src/proxy/formats/anthropic/index.ts +0 -13
  296. package/src/proxy/formats/anthropic/register.ts +0 -4
  297. package/src/proxy/formats/anthropic/schemas.ts +0 -6
  298. package/src/proxy/formats/anthropic/stream.ts +0 -205
  299. package/src/proxy/formats/handler.ts +0 -46
  300. package/src/proxy/formats/index.ts +0 -13
  301. package/src/proxy/formats/jsonSchema.ts +0 -2
  302. package/src/proxy/formats/openai/alibabaProvider.ts +0 -34
  303. package/src/proxy/formats/openai/codexMetadata.ts +0 -61
  304. package/src/proxy/formats/openai/handler.ts +0 -133
  305. package/src/proxy/formats/openai/index.ts +0 -33
  306. package/src/proxy/formats/openai/provider.ts +0 -50
  307. package/src/proxy/formats/openai/register.ts +0 -4
  308. package/src/proxy/formats/openai/schemas.ts +0 -25
  309. package/src/proxy/formats/openai/stream.ts +0 -600
  310. package/src/proxy/formats/openai/zhipuProvider.ts +0 -31
  311. package/src/proxy/formats/providerAdapter.ts +0 -50
  312. package/src/proxy/formats/providerRegistry.ts +0 -51
  313. package/src/proxy/formats/providers/index.ts +0 -3
  314. package/src/proxy/formats/registry.ts +0 -65
  315. package/src/proxy/groupEvidenceExporter.ts +0 -371
  316. package/src/proxy/groupStore.ts +0 -336
  317. package/src/proxy/handler.ts +0 -729
  318. package/src/proxy/identityProxy.ts +0 -1842
  319. package/src/proxy/jsonlRecovery.ts +0 -554
  320. package/src/proxy/logBodyChunks.ts +0 -91
  321. package/src/proxy/logFinalizer.ts +0 -777
  322. package/src/proxy/logFinalizer.worker.ts +0 -24
  323. package/src/proxy/logImportUpload.ts +0 -128
  324. package/src/proxy/logImporter.ts +0 -813
  325. package/src/proxy/logIndex.ts +0 -663
  326. package/src/proxy/logSearch.ts +0 -122
  327. package/src/proxy/logger.ts +0 -537
  328. package/src/proxy/openaiOrphanToolStrip.ts +0 -142
  329. package/src/proxy/platformCommands.ts +0 -253
  330. package/src/proxy/privateDataPath.ts +0 -419
  331. package/src/proxy/providerImporters.ts +0 -578
  332. package/src/proxy/providerScanStore.ts +0 -87
  333. package/src/proxy/providerSecretStore.ts +0 -216
  334. package/src/proxy/providers.ts +0 -998
  335. package/src/proxy/rawStreamCapture.ts +0 -342
  336. package/src/proxy/runFailures.ts +0 -94
  337. package/src/proxy/runStore.ts +0 -214
  338. package/src/proxy/runtimeAdmission.ts +0 -55
  339. package/src/proxy/runtimeHealth.ts +0 -221
  340. package/src/proxy/runtimeMetrics.ts +0 -149
  341. package/src/proxy/runtimeShutdown.ts +0 -157
  342. package/src/proxy/schemas.ts +0 -112
  343. package/src/proxy/sessionArchive.ts +0 -479
  344. package/src/proxy/sessionInfo.ts +0 -266
  345. package/src/proxy/sessionProcess.ts +0 -194
  346. package/src/proxy/sessionRuntime.ts +0 -121
  347. package/src/proxy/sessionSupervisor.ts +0 -305
  348. package/src/proxy/sessionWorkerEntry.ts +0 -26
  349. package/src/proxy/shutdownCoordinator.ts +0 -90
  350. package/src/proxy/socketTracker.ts +0 -639
  351. package/src/proxy/sqliteLogIndex.ts +0 -733
  352. package/src/proxy/storageLifecycle.ts +0 -432
  353. package/src/proxy/store.ts +0 -1773
  354. package/src/proxy/streamTiming.ts +0 -31
  355. package/src/proxy/toolSchemaWarnings.ts +0 -529
  356. package/src/proxy/uiAssetServer.ts +0 -106
  357. package/src/proxy/upstream.ts +0 -127
  358. package/src/services/alerts.ts +0 -12
  359. package/src/services/groups.ts +0 -119
  360. package/src/services/piAgent.ts +0 -421
  361. package/src/services/piAgentRetrieval.ts +0 -1180
  362. package/src/services/result.ts +0 -17
  363. package/src/services/runs.ts +0 -76
  364. package/src/ui/bootstrap.ts +0 -167
  365. package/src/ui/index.html +0 -38
  366. package/src/ui/logsStreamClient.ts +0 -20
  367. package/src/ui/main.tsx +0 -13
  368. package/src/ui/public/runtime-config.js +0 -2
  369. package/src/ui/router.tsx +0 -13
  370. package/src/ui/routes/__root.tsx +0 -69
  371. package/src/ui/routes/index.tsx +0 -35
  372. package/src/ui/routes/session/$sessionId.tsx +0 -50
  373. package/src/ui/runtimeDiscovery.ts +0 -50
@@ -1,639 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { readFile } from "node:fs/promises";
3
- import { promisify } from "node:util";
4
- import { z } from "zod";
5
- import { logger } from "./logger";
6
-
7
- const execFileAsync = promisify(execFile);
8
-
9
- type ClientInfo = {
10
- port: number | null;
11
- pid: number | null;
12
- cwd: string | null;
13
- projectFolder: string | null;
14
- };
15
-
16
- type CacheEntry = ClientInfo & {
17
- expiresAt: number;
18
- };
19
-
20
- type NetstatClient = {
21
- pid: number;
22
- localPort: number;
23
- };
24
-
25
- // Cache entries: port -> { pid, cwd, projectFolder, expiresAt }
26
- const cache = new Map<number, CacheEntry>();
27
- const CACHE_TTL_MS = 5 * 60 * 1000;
28
- const MAX_CACHE_SIZE = 200;
29
- const RemotePortSchema = z.number().int().min(1).max(65_535);
30
- const SocketSchema = z
31
- .object({
32
- remotePort: RemotePortSchema.nullish(),
33
- })
34
- .passthrough();
35
-
36
- // Deduplicate concurrent lookups for the same port
37
- const inflight = new Map<number, Promise<ClientInfo>>();
38
-
39
- // --- Recent-client scan cache -------------------------------------------
40
- // `getClientInfo` no longer needs a per-request client port; it scans all
41
- // current connections to our listen port and returns the freshest. We cache
42
- // the result for a short window so a burst of requests from one client
43
- // converges on a single subprocess call.
44
- const RECENT_CLIENT_TTL_MS = 1000;
45
- let recentInflight: Promise<ClientInfo> | null = null;
46
- let recentCache: ClientInfo | null = null;
47
- let recentCacheExpiresAt = 0;
48
-
49
- /** The port the proxy is listening on. Falls back to the dev default. */
50
- function getProxyPort(): number {
51
- // The proxy server exposes its chosen port via the `PROXY_PORT` env var.
52
- // Default to the well-known dev port so the scan still works in dev mode
53
- // (where `proxy.ts` reads from `getConfig()` instead of an env var).
54
- const raw = process.env["PROXY_PORT"];
55
- if (raw !== undefined) {
56
- const parsed = parseInt(raw, 10);
57
- if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65_535) return parsed;
58
- }
59
- return 9527;
60
- }
61
-
62
- function getRecentClientFromCache(): ClientInfo | null {
63
- if (recentCache === null) return null;
64
- if (Date.now() >= recentCacheExpiresAt) {
65
- recentCache = null;
66
- return null;
67
- }
68
- return recentCache;
69
- }
70
-
71
- function setRecentClientCache(info: ClientInfo): void {
72
- recentCache = info;
73
- recentCacheExpiresAt = Date.now() + RECENT_CLIENT_TTL_MS;
74
- }
75
-
76
- function evictCacheIfNeeded(): void {
77
- while (cache.size > MAX_CACHE_SIZE) {
78
- const oldestKey = cache.keys().next().value;
79
- if (oldestKey === undefined) break;
80
- cache.delete(oldestKey);
81
- }
82
- }
83
-
84
- function getFromCache(port: number): ClientInfo | null {
85
- const entry = cache.get(port);
86
- if (entry && Date.now() < entry.expiresAt) {
87
- return { port: entry.port, pid: entry.pid, cwd: entry.cwd, projectFolder: entry.projectFolder };
88
- }
89
- if (entry) cache.delete(port);
90
- return null;
91
- }
92
-
93
- function setCache(port: number, info: ClientInfo): void {
94
- cache.set(port, { ...info, expiresAt: Date.now() + CACHE_TTL_MS });
95
- evictCacheIfNeeded();
96
- }
97
-
98
- /**
99
- * Best-effort remote-port extraction across runtimes. Tries, in order:
100
- * 1. TanStack Start / Nitro: `request.context.nitro.node.req.socket` (h3
101
- * wraps the underlying Node request, where the socket lives).
102
- * 2. Bun-style: `request.socket` accessor.
103
- * 3. Direct `socket` / `connection` properties on the request (legacy Node
104
- * IncomingMessage-style wrapping).
105
- * Returns `null` when nothing matches 鈥?the caller will then leave `pid`
106
- * (and downstream `clientPid`) as `null`.
107
- */
108
- export function extractRemotePort(request: Request): number | null {
109
- const candidateSockets: unknown[] = [];
110
-
111
- // 1) Nitro / h3 path. TanStack Start attaches the h3 event under
112
- // `request.context.nitro`, and the underlying Node request lives at
113
- // `event.node.req`. The socket may be on `req.socket` or
114
- // `req.connection` depending on the Node version / TLS config.
115
- const requestAsUnknown: unknown = request;
116
- const ctx = pick(requestAsUnknown, "context");
117
- const nitro = pick(ctx, "nitro");
118
- const node = pick(nitro, "node");
119
- const innerReq = pick(node, "req");
120
- const sock = pick(innerReq, "socket") ?? pick(innerReq, "connection");
121
- if (sock !== undefined) candidateSockets.push(sock);
122
-
123
- // 2) Bun-style direct socket accessor on the Request.
124
- const descriptor = Object.getOwnPropertyDescriptor(request, "socket");
125
- if (descriptor !== undefined && descriptor.get === undefined) {
126
- candidateSockets.push(descriptor.value);
127
- }
128
-
129
- // 3) Raw socket/connection properties on the request itself.
130
- const directSocket = pick(requestAsUnknown, "socket");
131
- if (directSocket !== undefined) candidateSockets.push(directSocket);
132
- const directConnection = pick(requestAsUnknown, "connection");
133
- if (directConnection !== undefined) candidateSockets.push(directConnection);
134
-
135
- for (const candidate of candidateSockets) {
136
- const parsed = SocketSchema.safeParse(candidate);
137
- if (!parsed.success) continue;
138
- const remotePort = parsed.data.remotePort;
139
- if (remotePort === undefined || remotePort === null) continue;
140
- return remotePort;
141
- }
142
- return null;
143
- }
144
-
145
- function isObject(value: unknown): value is Record<string, unknown> {
146
- return value !== undefined && value !== null && typeof value === "object";
147
- }
148
-
149
- /** Read a property from an `unknown` without triggering lint rules that
150
- * forbid type assertions. Returns `undefined` for non-object inputs or
151
- * when the property is missing. */
152
- function pick(value: unknown, key: string): unknown {
153
- if (!isObject(value)) return undefined;
154
- return value[key];
155
- }
156
-
157
- function parseCommandLine(
158
- commandLine: string,
159
- sep: string,
160
- ): { cwd: string | null; projectFolder: string | null } {
161
- if (!commandLine) return { cwd: null, projectFolder: null };
162
- const exeMatch = commandLine.match(/^"([^"]+)"/) || commandLine.match(/^([^\s]+)/);
163
- if (exeMatch?.[1] !== undefined) {
164
- const exePath = exeMatch[1];
165
- const lastSep = exePath.lastIndexOf(sep);
166
- if (lastSep > 0) {
167
- const exeDir = exePath.substring(0, lastSep);
168
- return { cwd: exeDir, projectFolder: exeDir.split(sep).pop() ?? null };
169
- }
170
- }
171
- return { cwd: null, projectFolder: null };
172
- }
173
-
174
- /**
175
- * Get the working directory and command line for a process by PID.
176
- */
177
- async function lookupProcessInfo(
178
- pid: number,
179
- ): Promise<{ cwd: string | null; projectFolder: string | null }> {
180
- const platform = process.platform;
181
-
182
- try {
183
- if (platform === "win32") {
184
- const psScript = [
185
- `$proc = Get-CimInstance Win32_Process -Filter "ProcessId=${pid}" -ErrorAction SilentlyContinue`,
186
- `if ($proc) { Write-Output $proc.CommandLine }`,
187
- ].join("; ");
188
- const { stdout } = await execFileAsync(
189
- "powershell.exe",
190
- ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", psScript],
191
- {
192
- windowsHide: true,
193
- timeout: 3000,
194
- maxBuffer: 64 * 1024,
195
- },
196
- );
197
- const commandLine = stdout.trim();
198
- if (commandLine.length > 0) return parseCommandLine(commandLine, "\\");
199
- } else {
200
- try {
201
- const raw = await readFile(`/proc/${String(pid)}/cmdline`, "utf8");
202
- const commandLine = raw.replace(/\0/g, " ").trim();
203
- if (commandLine) {
204
- return parseCommandLine(commandLine, "/");
205
- }
206
- } catch {
207
- // Fall back to ps
208
- }
209
-
210
- if (platform === "darwin") {
211
- const { stdout } = await execFileAsync("ps", ["-p", String(pid), "-o", "comm="], {
212
- timeout: 3000,
213
- maxBuffer: 64 * 1024,
214
- });
215
- const exePath = stdout.trim();
216
- if (exePath) {
217
- return parseCommandLine(exePath, "/");
218
- }
219
- }
220
- }
221
- return { cwd: null, projectFolder: null };
222
- } catch {
223
- return { cwd: null, projectFolder: null };
224
- }
225
- }
226
-
227
- /**
228
- * Scan the OS connection table for the freshest client鈫抪roxy connection and
229
- * return the matching PID plus its cwd/projectFolder.
230
- */
231
- async function lookupRecentClient(): Promise<ClientInfo> {
232
- const port = getProxyPort();
233
- const platform = process.platform;
234
- try {
235
- if (platform === "win32") {
236
- // `Get-NetTCPConnection` can take several seconds on busy Windows
237
- // machines, which makes direct-port clients (notably MiMo Code) lose
238
- // their PID before the fallback completes. `netstat` streams quickly
239
- // and includes the owning process id for established loopback rows.
240
- const { stdout } = await execFileAsync("netstat.exe", ["-ano", "-p", "TCP"], {
241
- windowsHide: true,
242
- timeout: 2000,
243
- maxBuffer: 2 * 1024 * 1024,
244
- });
245
- const client = pickNewestClientFromWindowsNetstat(stdout.split("\n"), port);
246
- if (client === null) {
247
- return { port: null, pid: null, cwd: null, projectFolder: null };
248
- }
249
- const { cwd, projectFolder } = await lookupProcessInfo(client.pid);
250
- return {
251
- port: client.localPort,
252
- pid: client.pid,
253
- cwd,
254
- projectFolder,
255
- };
256
- }
257
-
258
- // Unix: netstat with -p prints `pid/program` next to each entry. Pick
259
- // the row matching our proxy port on the remote side (i.e. the client's
260
- // ephemeral port + the proxy's listen port). When multiple clients are
261
- // connected we take the highest ephemeral port 鈥?Linux/macOS assign
262
- // ports in ascending order, so the largest one is the freshest.
263
- const lines = await readUnixEstablishedConnectionLines(port);
264
- const clientPid = pickNewestClientPidFromNetstat(lines, port);
265
- if (clientPid === null) {
266
- return { port: null, pid: null, cwd: null, projectFolder: null };
267
- }
268
- const { cwd, projectFolder } = await lookupProcessInfo(clientPid);
269
- return { port: null, pid: clientPid, cwd, projectFolder };
270
- } catch (err) {
271
- logger.debug(`[socketTracker] Recent-client lookup failed for port ${port}:`, String(err));
272
- return { port: null, pid: null, cwd: null, projectFolder: null };
273
- }
274
- }
275
-
276
- /** Parse the output of `netstat -tanp` (or `ss -tanp`) and return the PID
277
- * on the most recently opened client-side connection to `proxyPort`. */
278
- function pickNewestClientPidFromNetstat(lines: string[], proxyPort: number): number | null {
279
- const proxyToken = `:${proxyPort}`;
280
- type Row = { pid: number; localPort: number; order: number };
281
- const candidates: Row[] = [];
282
- let order = 0;
283
- for (const line of lines) {
284
- order++;
285
- const cols = line.trim().split(/\s+/);
286
- if (cols.length < 5) continue;
287
- const localAddr = cols[3] ?? "";
288
- const remoteAddr = cols[4] ?? "";
289
- const pidField = cols[6] ?? "";
290
- // Client-side rows: `RemoteAddress` (5th col) is the proxy's listen port;
291
- // `LocalAddress` (4th col) is the client's ephemeral port.
292
- if (!remoteAddr.endsWith(proxyToken)) continue;
293
- const pidMatch = pidField.match(/^(\d+)/) ?? line.match(/pid=(\d+)/);
294
- if (pidMatch === null) continue;
295
- const pid = parseInt(pidMatch[1] ?? "", 10);
296
- if (!Number.isInteger(pid) || pid <= 0) continue;
297
- const localPortMatch = localAddr.match(/:(\d+)$/);
298
- const localPort = localPortMatch !== null ? parseInt(localPortMatch[1] ?? "", 10) : 0;
299
- candidates.push({ pid, localPort, order });
300
- }
301
- if (candidates.length === 0) return null;
302
- candidates.sort((a, b) => b.localPort - a.localPort || b.order - a.order);
303
- return candidates[0]?.pid ?? null;
304
- }
305
-
306
- async function readUnixEstablishedConnectionLines(port: number): Promise<string[]> {
307
- const commands = [
308
- { command: "netstat", args: ["-tanp"] },
309
- { command: "ss", args: ["-tanp"] },
310
- ];
311
- for (const spec of commands) {
312
- try {
313
- const { stdout } = await execFileAsync(spec.command, spec.args, {
314
- timeout: 2000,
315
- maxBuffer: 2 * 1024 * 1024,
316
- });
317
- const portToken = `:${port}`;
318
- const lines = stdout
319
- .trim()
320
- .split("\n")
321
- .filter((line) => line.includes(portToken) && line.includes("ESTAB"));
322
- if (lines.length > 0) return lines;
323
- } catch {
324
- // Try the next socket table command.
325
- }
326
- }
327
- return [];
328
- }
329
-
330
- function parseAddressPort(address: string): number | null {
331
- const match = address.match(/:(\d+)$/);
332
- if (match === null) return null;
333
- const parsed = parseInt(match[1] ?? "", 10);
334
- if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65_535) return null;
335
- return parsed;
336
- }
337
-
338
- function pickNewestClientFromWindowsNetstat(
339
- lines: string[],
340
- proxyPort: number,
341
- ): NetstatClient | null {
342
- const proxyToken = `:${proxyPort}`;
343
- type Row = NetstatClient & { order: number };
344
- const candidates: Row[] = [];
345
- let order = 0;
346
-
347
- for (const line of lines) {
348
- order++;
349
- const cols = line.trim().split(/\s+/);
350
- if (cols.length < 5) continue;
351
- const localAddr = cols[1] ?? "";
352
- const remoteAddr = cols[2] ?? "";
353
- const state = cols[3] ?? "";
354
- const pidStr = cols[4] ?? "";
355
- if (state !== "ESTABLISHED") continue;
356
- if (!remoteAddr.endsWith(proxyToken)) continue;
357
-
358
- const localPort = parseAddressPort(localAddr);
359
- if (localPort === null || localPort === proxyPort) continue;
360
-
361
- const pid = parseInt(pidStr, 10);
362
- if (!Number.isInteger(pid) || pid <= 0) continue;
363
- candidates.push({ pid, localPort, order });
364
- }
365
-
366
- if (candidates.length === 0) return null;
367
- candidates.sort((a, b) => b.localPort - a.localPort || b.order - a.order);
368
- const selected = candidates[0];
369
- if (selected === undefined) return null;
370
- return { pid: selected.pid, localPort: selected.localPort };
371
- }
372
-
373
- export function _pickNewestClientFromWindowsNetstatForTests(
374
- lines: string[],
375
- proxyPort: number,
376
- ): NetstatClient | null {
377
- return pickNewestClientFromWindowsNetstat(lines, proxyPort);
378
- }
379
-
380
- export function _pickNewestClientPidFromNetstatForTests(
381
- lines: string[],
382
- proxyPort: number,
383
- ): number | null {
384
- return pickNewestClientPidFromNetstat(lines, proxyPort);
385
- }
386
-
387
- /** Custom request headers the client (or a wrapper script) may set so we
388
- * can identify the originating process without scanning the OS connection
389
- * table. Recognized names:
390
- * - `X-Agent-Inspector-Client-Pid`
391
- * - `X-Agent-Inspector-Client-Port`
392
- * - `X-Agent-Inspector-Client-Cwd`
393
- * - `X-Agent-Inspector-Client-Project-Folder`
394
- * Clients should set these via a small launcher script that exports the
395
- * values from `$$` and `pwd` before invoking the AI tool, e.g.:
396
- *
397
- * curl -H "X-Agent-Inspector-Client-Pid: $$" \
398
- * -H "X-Agent-Inspector-Client-Cwd: $(pwd)" ...
399
- */
400
- const CLIENT_HEADERS = {
401
- pid: "x-agent-inspector-client-pid",
402
- port: "x-agent-inspector-client-port",
403
- cwd: "x-agent-inspector-client-cwd",
404
- projectFolder: "x-agent-inspector-client-project-folder",
405
- } as const;
406
-
407
- /** Try to read a positive-integer value out of a header. Returns `null` for
408
- * missing / malformed values. */
409
- function readIntHeader(headers: Headers, name: string): number | null {
410
- const raw = headers.get(name);
411
- if (raw === null || raw === "") return null;
412
- const parsed = parseInt(raw, 10);
413
- if (!Number.isInteger(parsed) || parsed < 0) return null;
414
- return parsed;
415
- }
416
-
417
- /** Read client-supplied headers. Returns `null` when no PID header is
418
- * present 鈥?caller falls back to OS scanning. */
419
- function readClientHeaders(request: Request): ClientInfo | null {
420
- const pid = readIntHeader(request.headers, CLIENT_HEADERS.pid);
421
- if (pid === null || pid === 0) return null;
422
- const port = readIntHeader(request.headers, CLIENT_HEADERS.port);
423
- const cwd = request.headers.get(CLIENT_HEADERS.cwd);
424
- const projectFolder = request.headers.get(CLIENT_HEADERS.projectFolder);
425
- return {
426
- pid,
427
- port: port !== null && port > 0 ? port : null,
428
- cwd: cwd !== null && cwd !== "" ? cwd : null,
429
- projectFolder: projectFolder !== null && projectFolder !== "" ? projectFolder : null,
430
- };
431
- }
432
-
433
- /**
434
- * Get client info (PID, CWD, project folder) for the request that triggered
435
- * this call. The TanStack Start fetch-API `Request` does not expose the
436
- * underlying TCP socket (no `request.socket`, no `event.node.req`), so we
437
- * cannot derive the per-request client port from the request itself.
438
- *
439
- * Strategy: enumerate the OS-level TCP connections to our proxy's listen
440
- * port and pick the most recently established client-side connection. This
441
- * is essentially "who is currently talking to us" 鈥?for a session with many
442
- * rapid-fire requests the same client holds a long-lived connection, so
443
- * consecutive lookups within the cache TTL converge on the same PID.
444
- *
445
- * Deduplicates concurrent lookups with a single in-flight promise. Results
446
- * are cached by PID for `RECENT_CLIENT_TTL_MS` to avoid spawning a
447
- * `Get-NetTCPConnection` / `ss` subprocess on every single request.
448
- */
449
- export async function getClientInfo(request: Request): Promise<ClientInfo> {
450
- // Preferred path: explicit client identity in headers. AI coding tools
451
- // (or the wrapper scripts that launch them) attach
452
- // `X-Agent-Inspector-Client-Pid` (and optionally 鈥?Cwd /
453
- // 鈥?Project-Folder / 鈥?Port) so we never have to guess.
454
- const fromHeaders = readClientHeaders(request);
455
- if (fromHeaders !== null) return fromHeaders;
456
-
457
- const remotePort = extractRemotePort(request);
458
- if (remotePort !== null) {
459
- const cachedPort = getFromCache(remotePort);
460
- if (cachedPort !== null) return cachedPort;
461
-
462
- const existingPortLookup = inflight.get(remotePort);
463
- if (existingPortLookup !== undefined) return existingPortLookup;
464
-
465
- const portLookup = lookupClientByPort(remotePort)
466
- .then((info) => {
467
- setCache(remotePort, info);
468
- inflight.delete(remotePort);
469
- return info;
470
- })
471
- .catch((err) => {
472
- inflight.delete(remotePort);
473
- logger.debug(`[socketTracker] Remote-port lookup failed for ${remotePort}:`, String(err));
474
- const fallback: ClientInfo = {
475
- port: remotePort,
476
- pid: null,
477
- cwd: null,
478
- projectFolder: null,
479
- };
480
- setCache(remotePort, fallback);
481
- return fallback;
482
- });
483
-
484
- inflight.set(remotePort, portLookup);
485
- return portLookup;
486
- }
487
-
488
- // Fallback: scan the OS connection table for the most recently opened
489
- // client鈫抪roxy connection. Best-effort 鈥?in environments with many
490
- // long-lived keep-alive connections (browsers, background processes) the
491
- // picked connection may not belong to the current request.
492
- const cached = getRecentClientFromCache();
493
- if (cached !== null) return cached;
494
-
495
- const existing = recentInflight;
496
- if (existing) return existing;
497
-
498
- const promise = lookupRecentClient()
499
- .then((info) => {
500
- setRecentClientCache(info);
501
- recentInflight = null;
502
- return info;
503
- })
504
- .catch((err) => {
505
- recentInflight = null;
506
- logger.debug(`[socketTracker] Recent-client lookup failed:`, String(err));
507
- return { port: null, pid: null, cwd: null, projectFolder: null };
508
- });
509
-
510
- recentInflight = promise;
511
- return promise;
512
- }
513
-
514
- /**
515
- * Lookup the PID owning a specific client-side local port, then enrich it
516
- * with the process's cwd / project folder. Used by the identity-injecting
517
- * native HTTP proxy in `identityProxy.ts`, which has access to
518
- * `req.socket.remotePort` (the client's ephemeral port) and needs to know
519
- * which process opened the connection.
520
- *
521
- * Results are cached by `clientPort` for `PORT_LOOKUP_TTL_MS` 鈥?a single
522
- * keep-alive connection typically reuses the same client port for many
523
- * requests, so we don't want to spawn a subprocess per request.
524
- */
525
- const PORT_LOOKUP_TTL_MS = 5 * 60 * 1000;
526
- const portInfoCache = new Map<number, { info: ClientInfo; expiresAt: number }>();
527
-
528
- export async function lookupClientByPort(clientPort: number): Promise<ClientInfo> {
529
- if (!Number.isInteger(clientPort) || clientPort <= 0 || clientPort > 65_535) {
530
- return { port: null, pid: null, cwd: null, projectFolder: null };
531
- }
532
- const cached = portInfoCache.get(clientPort);
533
- if (cached !== undefined && Date.now() < cached.expiresAt) {
534
- return cached.info;
535
- }
536
- try {
537
- const pid = await lookupPidByLocalPort(clientPort);
538
- if (pid === null) {
539
- const fallback: ClientInfo = {
540
- port: clientPort,
541
- pid: null,
542
- cwd: null,
543
- projectFolder: null,
544
- };
545
- portInfoCache.set(clientPort, { info: fallback, expiresAt: Date.now() + PORT_LOOKUP_TTL_MS });
546
- return fallback;
547
- }
548
- const { cwd, projectFolder } = await lookupProcessInfo(pid);
549
- const info: ClientInfo = { port: clientPort, pid, cwd, projectFolder };
550
- portInfoCache.set(clientPort, { info, expiresAt: Date.now() + PORT_LOOKUP_TTL_MS });
551
- return info;
552
- } catch (err) {
553
- logger.debug(`[socketTracker] lookupClientByPort(${clientPort}) failed:`, String(err));
554
- return { port: clientPort, pid: null, cwd: null, projectFolder: null };
555
- }
556
- }
557
-
558
- /** Returns the PID owning `clientPort` on the local machine, or `null`
559
- * if the port isn't held by any known process. Cross-platform wrapper
560
- * around the OS-specific connection table. */
561
- async function lookupPidByLocalPort(clientPort: number): Promise<number | null> {
562
- const platform = process.platform;
563
- try {
564
- if (platform === "win32") {
565
- // `Get-NetTCPConnection -LocalPort` can enumerate the entire socket table
566
- // before filtering on busy machines. `netstat.exe` streams quickly and
567
- // avoids PowerShell process startup for this hot lookup path.
568
- //
569
- // `netstat -ano` lists every TCP connection twice: once for each endpoint.
570
- //
571
- // Proto LocalAddress RemoteAddress State PID
572
- // TCP [::1]:25949 [::1]:55193 ESTABLISHED 10936 鈫?server-side row (proxy owns the listener)
573
- // TCP [::1]:55193 [::1]:25949 ESTABLISHED 29356 鈫?client-side row (curl owns the ephemeral port)
574
- //
575
- // For `clientPort` (the client's ephemeral port) we want the row
576
- // whose LOCAL column ends with `:<clientPort>` 鈥?that's the row
577
- // attributed to the client process.
578
- const { stdout } = await execFileAsync("netstat.exe", ["-ano", "-p", "TCP"], {
579
- windowsHide: true,
580
- timeout: 2000,
581
- maxBuffer: 2 * 1024 * 1024,
582
- });
583
- const clientToken = `:${clientPort}`;
584
- for (const rawLine of stdout.split("\n")) {
585
- const line = rawLine.trim();
586
- if (line === "") continue;
587
- const cols = line.split(/\s+/);
588
- if (cols.length < 5) continue;
589
- const localAddr = cols[1] ?? "";
590
- const state = cols[3] ?? "";
591
- if (state !== "ESTABLISHED") continue;
592
- if (!localAddr.endsWith(clientToken)) continue;
593
- const pidStr = cols[cols.length - 1] ?? "";
594
- const parsed = parseInt(pidStr, 10);
595
- if (Number.isInteger(parsed) && parsed > 0) return parsed;
596
- }
597
- return null;
598
- }
599
- // Unix: try `ss` first, fall back to `lsof`. Both need root or
600
- // CAP_NET_ADMIN to see PIDs of processes owned by other users; when
601
- // permission is denied the command exits non-zero and we return null.
602
- try {
603
- const { stdout } = await execFileAsync(
604
- "ss",
605
- ["-tanp", "state", "established", "sport", `= :${clientPort}`],
606
- {
607
- timeout: 2000,
608
- maxBuffer: 2 * 1024 * 1024,
609
- },
610
- );
611
- const match = stdout.match(/pid=(\d+)/);
612
- if (match !== null) {
613
- const parsed = parseInt(match[1] ?? "", 10);
614
- if (Number.isInteger(parsed) && parsed > 0) return parsed;
615
- }
616
- } catch {
617
- // fall through
618
- }
619
- try {
620
- const { stdout } = await execFileAsync(
621
- "lsof",
622
- ["-ti", `tcp:${clientPort}`, "-sTCP:ESTABLISHED"],
623
- {
624
- timeout: 2000,
625
- maxBuffer: 64 * 1024,
626
- },
627
- );
628
- const trimmed = stdout.trim().split("\n")[0] ?? "";
629
- const parsed = parseInt(trimmed, 10);
630
- if (Number.isInteger(parsed) && parsed > 0) return parsed;
631
- } catch {
632
- // fall through
633
- }
634
- return null;
635
- } catch (err) {
636
- logger.debug(`[socketTracker] lookupPidByLocalPort(${clientPort}) failed:`, String(err));
637
- return null;
638
- }
639
- }