@tonyclaw/agent-inspector 3.0.45 → 3.0.46

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 (338) hide show
  1. package/.output/{nitro.json → backend/nitro.json} +3 -3
  2. package/.output/backend-public/assets/index-D4LX-sw-.js +11 -0
  3. package/.output/cli.js +2326 -516
  4. package/.output/server/_libs/extend.mjs +4 -8
  5. package/.output/server/_libs/jszip.mjs +9 -35
  6. package/.output/server/_libs/react-dom.mjs +2 -3
  7. package/.output/server/_libs/react.mjs +1 -27
  8. package/.output/server/_libs/tanstack__react-router.mjs +1 -329
  9. package/.output/server/_libs/tanstack__router-core.mjs +42 -100
  10. package/.output/server/_libs/use-sync-external-store.mjs +1 -64
  11. package/.output/server/_libs/ws.mjs +1 -1
  12. package/.output/server/_ssr/index.mjs +4 -4
  13. package/.output/server/_ssr/{publicBasePathContext-5264L3rX.mjs → publicBasePathContext-IH2ilOfu.mjs} +52 -24
  14. package/.output/server/_ssr/{router-BJ_ltLim.mjs → router-D18yUq36.mjs} +19770 -19447
  15. package/.output/server/_ssr/{start-C6MjPjxp.mjs → start-COQu_AgD.mjs} +1 -1
  16. package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +4 -0
  17. package/.output/server/index.mjs +5 -145
  18. package/.output/{public/assets/CompareDrawer-B3_TLTJh.js → ui/assets/CompareDrawer-DAUuIJ6G.js} +1 -1
  19. package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-BRBVjOWI.js} +1 -1
  20. package/.output/ui/assets/ProxyViewerContainer-D7Sq0ctc.js +59 -0
  21. package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-nDLsjOhs.js} +1 -1
  22. package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-zd4BDgL0.js} +1 -1
  23. package/.output/ui/assets/ResponseView-AdIXGcSn.js +2 -0
  24. package/.output/ui/assets/StreamingChunkSequence-CesTz8He.js +1 -0
  25. package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-BAfzrhSU.js} +1 -1
  26. package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-QnBomgPD.js} +1 -1
  27. package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
  28. package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-BcsdxBAd.js} +1 -1
  29. package/.output/ui/assets/index-BvXp42al.css +1 -0
  30. package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-D0rtCN9V.js} +1 -1
  31. package/.output/ui/assets/index-DHpwr08Z.js +73 -0
  32. package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-dNuk2dsU.js} +5 -5
  33. package/.output/ui/assets/json-viewer-BvFHglMb.js +1 -0
  34. package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B5Z7gNZB.js} +1 -1
  35. package/.output/ui/index.html +229 -0
  36. package/.output/ui/runtime-config.js +2 -0
  37. package/.output/workers/logFinalizer.worker.js +979 -414
  38. package/.output/workers/sessionWorkerEntry.js +979 -414
  39. package/README.md +175 -43
  40. package/docs/instance-control.openapi.yaml +59 -4
  41. package/package.json +25 -5
  42. package/scripts/generate-theme-scales.mjs +3 -3
  43. package/src/{router.tsx → backend/router.tsx} +3 -15
  44. package/src/backend/routes/$.ts +26 -0
  45. package/src/backend/routes/__root.ts +4 -0
  46. package/src/{routes → backend/routes}/api/-instances.ts +3 -3
  47. package/src/{routes → backend/routes}/api/alerts.summary.ts +3 -3
  48. package/src/{routes → backend/routes}/api/alerts.ts +4 -4
  49. package/src/{routes → backend/routes}/api/config.paths.ts +1 -1
  50. package/src/{routes → backend/routes}/api/config.ts +11 -2
  51. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.help.ts +1 -1
  52. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.install.ts +1 -1
  53. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.runner-presets.ts +1 -1
  54. package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.upgrade.ts +1 -1
  55. package/src/{routes → backend/routes}/api/ecosystem.packages.ts +5 -1
  56. package/src/{routes → backend/routes}/api/ecosystem.recipes.$recipeId.run.ts +1 -1
  57. package/src/{routes → backend/routes}/api/ecosystem.tasks.$taskId.ts +1 -1
  58. package/src/{routes → backend/routes}/api/ecosystem.tasks.ts +1 -1
  59. package/src/{routes → backend/routes}/api/groups.$groupId.evidence.ts +2 -2
  60. package/src/{routes → backend/routes}/api/groups.$groupId.sessions.ts +2 -2
  61. package/src/{routes → backend/routes}/api/groups.$groupId.ts +2 -2
  62. package/src/{routes → backend/routes}/api/groups.ts +2 -2
  63. package/src/{routes → backend/routes}/api/health.ts +3 -3
  64. package/src/{routes → backend/routes}/api/instances.$name.connection.ts +1 -1
  65. package/src/{routes → backend/routes}/api/instances.$name.restart.ts +2 -2
  66. package/src/{routes → backend/routes}/api/instances.$name.start.ts +2 -2
  67. package/src/{routes → backend/routes}/api/instances.$name.stop.ts +2 -2
  68. package/src/{routes → backend/routes}/api/instances.$name.ts +1 -1
  69. package/src/{routes → backend/routes}/api/instances.ts +1 -1
  70. package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.promote.ts +2 -2
  71. package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.ts +2 -2
  72. package/src/{routes → backend/routes}/api/knowledge.candidates.ts +1 -1
  73. package/src/{routes → backend/routes}/api/knowledge.project-context.ts +2 -2
  74. package/src/{routes → backend/routes}/api/knowledge.search.ts +2 -2
  75. package/src/{routes → backend/routes}/api/knowledge.sessions.$sessionId.candidates.ts +3 -3
  76. package/src/{routes → backend/routes}/api/logs.$id.body.ts +6 -6
  77. package/src/{routes → backend/routes}/api/logs.$id.chunks.ts +5 -4
  78. package/src/{routes → backend/routes}/api/logs.$id.replay.ts +12 -11
  79. package/src/{routes → backend/routes}/api/logs.$id.ts +4 -3
  80. package/src/{routes → backend/routes}/api/logs.import.ts +3 -3
  81. package/src/{routes → backend/routes}/api/logs.stream.ts +11 -7
  82. package/src/{routes → backend/routes}/api/logs.ts +17 -9
  83. package/src/{routes → backend/routes}/api/mcp.ts +1 -1
  84. package/src/{routes → backend/routes}/api/models.ts +1 -1
  85. package/src/{routes → backend/routes}/api/pi-agent.ts +2 -2
  86. package/src/{routes → backend/routes}/api/providers.$providerId.model-metadata.ts +5 -5
  87. package/src/{routes → backend/routes}/api/providers.$providerId.test.log.ts +7 -7
  88. package/src/{routes → backend/routes}/api/providers.$providerId.ts +4 -4
  89. package/src/{routes → backend/routes}/api/providers.export.ts +1 -1
  90. package/src/{routes → backend/routes}/api/providers.import.ts +3 -3
  91. package/src/{routes → backend/routes}/api/providers.scan.ts +2 -2
  92. package/src/{routes → backend/routes}/api/providers.ts +9 -4
  93. package/src/{routes → backend/routes}/api/runs.$runId.evidence.ts +2 -2
  94. package/src/{routes → backend/routes}/api/runs.$runId.ts +2 -2
  95. package/src/{routes → backend/routes}/api/runs.ts +4 -4
  96. package/src/backend/routes/api/runtime.ts +46 -0
  97. package/src/{routes → backend/routes}/api/sessions.ts +3 -3
  98. package/src/{routes → backend/routes}/proxy/$.ts +1 -1
  99. package/src/{routes → backend/routes}/readyz.ts +1 -1
  100. package/src/{start.ts → backend/start.ts} +1 -1
  101. package/src/cli/alias.ts +2 -11
  102. package/src/cli/doctor.ts +32 -9
  103. package/src/cli/instance.ts +26 -13
  104. package/src/cli/instanceArgs.ts +30 -11
  105. package/src/cli/instanceControl.ts +0 -2
  106. package/src/cli/instanceModel.ts +9 -17
  107. package/src/cli/instanceRegistry.ts +0 -9
  108. package/src/cli/networkHints.ts +27 -14
  109. package/src/cli/onboard.ts +69 -2
  110. package/src/cli/onboardTarget.ts +89 -40
  111. package/src/cli/templates/codex-skill-onboard.ts +44 -14
  112. package/src/cli/templates/skill-onboard.ts +64 -28
  113. package/src/cli.ts +184 -61
  114. package/src/components/ProxyViewer.tsx +11 -9
  115. package/src/components/ProxyViewerContainer.tsx +12 -11
  116. package/src/components/pi-agent/PiAgentPanel.tsx +2 -2
  117. package/src/components/providers/ProviderCard.tsx +1 -1
  118. package/src/components/providers/ProviderTestResultsView.tsx +1 -1
  119. package/src/components/providers/SettingsDialog.tsx +159 -144
  120. package/src/components/proxy-viewer/ApplicationBar.tsx +2 -1
  121. package/src/components/proxy-viewer/LogEntry.tsx +1 -1
  122. package/src/components/proxy-viewer/log-formats/index.ts +1 -1
  123. package/src/contracts/index.ts +5 -0
  124. package/src/contracts/log.ts +33 -3
  125. package/src/knowledge/types.ts +3 -2
  126. package/src/lib/alertContract.ts +2 -2
  127. package/src/lib/apiClient.ts +30 -7
  128. package/src/lib/apiFormat.ts +29 -0
  129. package/src/lib/basePath.ts +29 -11
  130. package/src/lib/browserRuntimeContract.ts +124 -0
  131. package/src/lib/browserRuntimeResolver.ts +244 -0
  132. package/src/{proxy → lib}/claudeCodeStrip.ts +6 -7
  133. package/src/lib/codingAgentEndpoints.ts +38 -0
  134. package/src/lib/instanceContract.ts +27 -11
  135. package/src/lib/piAgentContract.ts +3 -2
  136. package/src/lib/publicBasePathContext.ts +40 -10
  137. package/src/lib/resourceLimits.ts +2 -0
  138. package/src/lib/runContract.ts +3 -3
  139. package/src/lib/sessionInfoContract.ts +3 -2
  140. package/src/mcp/instanceHandlers.ts +1 -0
  141. package/src/mcp/server.ts +19 -24
  142. package/src/proxy/config.ts +25 -30
  143. package/src/proxy/dataDir.ts +43 -7
  144. package/src/proxy/ecosystemTasks.ts +161 -70
  145. package/src/proxy/formats/registry.ts +6 -21
  146. package/src/proxy/handler.ts +129 -29
  147. package/src/proxy/identityProxy.ts +419 -39
  148. package/src/proxy/jsonlRecovery.ts +554 -0
  149. package/src/proxy/logFinalizer.ts +25 -10
  150. package/src/proxy/logImporter.ts +2 -1
  151. package/src/proxy/logIndex.ts +191 -95
  152. package/src/proxy/logger.ts +77 -14
  153. package/src/proxy/platformCommands.ts +41 -0
  154. package/src/proxy/privateDataPath.ts +310 -88
  155. package/src/proxy/rawStreamCapture.ts +194 -13
  156. package/src/proxy/runtimeAdmission.ts +5 -2
  157. package/src/proxy/runtimeHealth.ts +14 -1
  158. package/src/proxy/runtimeShutdown.ts +11 -1
  159. package/src/proxy/schemas.ts +7 -1
  160. package/src/proxy/sessionArchive.ts +78 -14
  161. package/src/proxy/sqliteLogIndex.ts +65 -2
  162. package/src/proxy/store.ts +141 -34
  163. package/src/proxy/uiAssetServer.ts +106 -0
  164. package/src/proxy/upstream.ts +29 -2
  165. package/src/ui/bootstrap.ts +167 -0
  166. package/src/ui/index.html +38 -0
  167. package/src/ui/logsStreamClient.ts +20 -0
  168. package/src/ui/main.tsx +13 -0
  169. package/src/ui/public/runtime-config.js +2 -0
  170. package/src/ui/router.tsx +13 -0
  171. package/src/ui/routes/__root.tsx +69 -0
  172. package/src/{routes → ui/routes}/index.tsx +2 -3
  173. package/src/{routes → ui/routes}/session/$sessionId.tsx +5 -11
  174. package/src/ui/runtimeDiscovery.ts +50 -0
  175. package/styles/globals.css +1 -0
  176. package/.output/public/assets/ProxyViewerContainer-BEAhuGOJ.js +0 -126
  177. package/.output/public/assets/ResponseView-DBCjkwye.js +0 -2
  178. package/.output/public/assets/StreamingChunkSequence-CuXhDgMo.js +0 -1
  179. package/.output/public/assets/index-BZo_8koU.js +0 -70
  180. package/.output/public/assets/index-ZWschkCh.css +0 -1
  181. package/.output/public/assets/json-viewer-B-Ao6pwW.js +0 -1
  182. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +0 -1
  183. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +0 -344
  184. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +0 -2
  185. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +0 -2
  186. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +0 -2
  187. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +0 -11
  188. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +0 -69
  189. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +0 -1
  190. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +0 -28
  191. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +0 -6
  192. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +0 -34
  193. package/.output/server/_libs/aria-hidden.mjs +0 -122
  194. package/.output/server/_libs/bail.mjs +0 -8
  195. package/.output/server/_libs/character-entities.mjs +0 -2130
  196. package/.output/server/_libs/class-variance-authority.mjs +0 -44
  197. package/.output/server/_libs/clsx.mjs +0 -16
  198. package/.output/server/_libs/comma-separated-tokens.mjs +0 -10
  199. package/.output/server/_libs/decode-named-character-reference+[...].mjs +0 -8
  200. package/.output/server/_libs/dequal.mjs +0 -27
  201. package/.output/server/_libs/detect-node-es.mjs +0 -1
  202. package/.output/server/_libs/devlop.mjs +0 -8
  203. package/.output/server/_libs/diff.mjs +0 -320
  204. package/.output/server/_libs/estree-util-is-identifier-name.mjs +0 -11
  205. package/.output/server/_libs/floating-ui__core.mjs +0 -725
  206. package/.output/server/_libs/floating-ui__dom.mjs +0 -622
  207. package/.output/server/_libs/floating-ui__react-dom.mjs +0 -292
  208. package/.output/server/_libs/floating-ui__utils.mjs +0 -320
  209. package/.output/server/_libs/get-nonce.mjs +0 -9
  210. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
  211. package/.output/server/_libs/hast-util-whitespace.mjs +0 -10
  212. package/.output/server/_libs/html-url-attributes.mjs +0 -26
  213. package/.output/server/_libs/inline-style-parser.mjs +0 -142
  214. package/.output/server/_libs/is-plain-obj.mjs +0 -10
  215. package/.output/server/_libs/lucide-react.mjs +0 -1027
  216. package/.output/server/_libs/mdast-util-from-markdown.mjs +0 -717
  217. package/.output/server/_libs/mdast-util-to-hast.mjs +0 -710
  218. package/.output/server/_libs/mdast-util-to-string.mjs +0 -38
  219. package/.output/server/_libs/micromark-core-commonmark.mjs +0 -2259
  220. package/.output/server/_libs/micromark-factory-destination.mjs +0 -94
  221. package/.output/server/_libs/micromark-factory-label.mjs +0 -63
  222. package/.output/server/_libs/micromark-factory-space.mjs +0 -24
  223. package/.output/server/_libs/micromark-factory-title.mjs +0 -65
  224. package/.output/server/_libs/micromark-factory-whitespace.mjs +0 -22
  225. package/.output/server/_libs/micromark-util-character.mjs +0 -44
  226. package/.output/server/_libs/micromark-util-chunked.mjs +0 -36
  227. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +0 -12
  228. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
  229. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
  230. package/.output/server/_libs/micromark-util-decode-string.mjs +0 -21
  231. package/.output/server/_libs/micromark-util-encode.mjs +0 -1
  232. package/.output/server/_libs/micromark-util-html-tag-name.mjs +0 -69
  233. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
  234. package/.output/server/_libs/micromark-util-resolve-all.mjs +0 -15
  235. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +0 -41
  236. package/.output/server/_libs/micromark-util-subtokenize.mjs +0 -346
  237. package/.output/server/_libs/micromark.mjs +0 -906
  238. package/.output/server/_libs/property-information.mjs +0 -1209
  239. package/.output/server/_libs/radix-ui.mjs +0 -1
  240. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  241. package/.output/server/_libs/radix-ui__primitive.mjs +0 -11
  242. package/.output/server/_libs/radix-ui__react-accordion.mjs +0 -1
  243. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +0 -1
  244. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  245. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +0 -1
  246. package/.output/server/_libs/radix-ui__react-avatar.mjs +0 -1
  247. package/.output/server/_libs/radix-ui__react-checkbox.mjs +0 -1
  248. package/.output/server/_libs/radix-ui__react-collapsible.mjs +0 -144
  249. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -69
  250. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +0 -38
  251. package/.output/server/_libs/radix-ui__react-context-menu.mjs +0 -1
  252. package/.output/server/_libs/radix-ui__react-context.mjs +0 -64
  253. package/.output/server/_libs/radix-ui__react-dialog.mjs +0 -280
  254. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -9
  255. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +0 -1
  256. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +0 -39
  257. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +0 -210
  258. package/.output/server/_libs/radix-ui__react-form.mjs +0 -1
  259. package/.output/server/_libs/radix-ui__react-hover-card.mjs +0 -1
  260. package/.output/server/_libs/radix-ui__react-id.mjs +0 -14
  261. package/.output/server/_libs/radix-ui__react-label.mjs +0 -1
  262. package/.output/server/_libs/radix-ui__react-menu.mjs +0 -1
  263. package/.output/server/_libs/radix-ui__react-menubar.mjs +0 -1
  264. package/.output/server/_libs/radix-ui__react-popover.mjs +0 -1
  265. package/.output/server/_libs/radix-ui__react-popper.mjs +0 -340
  266. package/.output/server/_libs/radix-ui__react-portal.mjs +0 -16
  267. package/.output/server/_libs/radix-ui__react-presence.mjs +0 -173
  268. package/.output/server/_libs/radix-ui__react-primitive.mjs +0 -42
  269. package/.output/server/_libs/radix-ui__react-progress.mjs +0 -1
  270. package/.output/server/_libs/radix-ui__react-radio-group.mjs +0 -1
  271. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +0 -235
  272. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +0 -731
  273. package/.output/server/_libs/radix-ui__react-select.mjs +0 -1207
  274. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -28
  275. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -1
  276. package/.output/server/_libs/radix-ui__react-slot.mjs +0 -132
  277. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -1
  278. package/.output/server/_libs/radix-ui__react-tabs.mjs +0 -194
  279. package/.output/server/_libs/radix-ui__react-toast.mjs +0 -2
  280. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +0 -1
  281. package/.output/server/_libs/radix-ui__react-toggle.mjs +0 -1
  282. package/.output/server/_libs/radix-ui__react-toolbar.mjs +0 -1
  283. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -498
  284. package/.output/server/_libs/radix-ui__react-use-previous.mjs +0 -14
  285. package/.output/server/_libs/radix-ui__react-use-size.mjs +0 -39
  286. package/.output/server/_libs/react-markdown.mjs +0 -235
  287. package/.output/server/_libs/react-remove-scroll-bar.mjs +0 -82
  288. package/.output/server/_libs/react-remove-scroll.mjs +0 -328
  289. package/.output/server/_libs/react-style-singleton.mjs +0 -69
  290. package/.output/server/_libs/remark-parse.mjs +0 -19
  291. package/.output/server/_libs/remark-rehype.mjs +0 -21
  292. package/.output/server/_libs/space-separated-tokens.mjs +0 -6
  293. package/.output/server/_libs/style-to-js.mjs +0 -72
  294. package/.output/server/_libs/style-to-object.mjs +0 -38
  295. package/.output/server/_libs/swr.mjs +0 -947
  296. package/.output/server/_libs/tailwind-merge.mjs +0 -3255
  297. package/.output/server/_libs/tanstack__react-virtual.mjs +0 -130
  298. package/.output/server/_libs/tanstack__virtual-core.mjs +0 -1319
  299. package/.output/server/_libs/three.mjs +0 -26016
  300. package/.output/server/_libs/trim-lines.mjs +0 -41
  301. package/.output/server/_libs/trough.mjs +0 -85
  302. package/.output/server/_libs/tslib.mjs +0 -1
  303. package/.output/server/_libs/ungap__structured-clone.mjs +0 -212
  304. package/.output/server/_libs/unified.mjs +0 -661
  305. package/.output/server/_libs/unist-util-is.mjs +0 -100
  306. package/.output/server/_libs/unist-util-position.mjs +0 -27
  307. package/.output/server/_libs/unist-util-stringify-position.mjs +0 -27
  308. package/.output/server/_libs/unist-util-visit-parents.mjs +0 -82
  309. package/.output/server/_libs/unist-util-visit.mjs +0 -24
  310. package/.output/server/_libs/use-callback-ref.mjs +0 -66
  311. package/.output/server/_libs/use-sidecar.mjs +0 -106
  312. package/.output/server/_libs/vfile-message.mjs +0 -138
  313. package/.output/server/_libs/vfile.mjs +0 -467
  314. package/.output/server/_sessionId-CJ99gZFV.mjs +0 -84
  315. package/.output/server/_sessionId-DEjScBpK.mjs +0 -146
  316. package/.output/server/_ssr/CompareDrawer-DtOIMeQB.mjs +0 -1060
  317. package/.output/server/_ssr/InspectorPet-CXc4oCBQ.mjs +0 -938
  318. package/.output/server/_ssr/ProxyViewerContainer-DVPlwfkt.mjs +0 -17632
  319. package/.output/server/_ssr/ReplayDialog-BQEA95qC.mjs +0 -725
  320. package/.output/server/_ssr/RequestAnatomy-BMyd8B7Q.mjs +0 -837
  321. package/.output/server/_ssr/ResponseView-qrTaEIM8.mjs +0 -757
  322. package/.output/server/_ssr/StreamingChunkSequence-BFTL8mIy.mjs +0 -274
  323. package/.output/server/_ssr/index-Bcj1Hcy4.mjs +0 -135
  324. package/.output/server/_ssr/index-DW-M7XUG.mjs +0 -84
  325. package/.output/server/_ssr/json-viewer-DiI0Yjsn.mjs +0 -483
  326. package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +0 -4
  327. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  328. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  329. package/.output/server/node_modules/tslib/package.json +0 -47
  330. package/.output/server/node_modules/tslib/tslib.js +0 -484
  331. package/.output/server/package.json +0 -9
  332. package/src/routes/__root.tsx +0 -170
  333. /package/.output/{public → ui}/assets/agent-inspector-58K1_MsC.ico +0 -0
  334. /package/.output/{public → ui}/assets/alibaba-TTwafVwX.svg +0 -0
  335. /package/.output/{public → ui}/assets/minimax-BPMzvuL-.jpeg +0 -0
  336. /package/.output/{public → ui}/assets/qwen-mMn3f5ul.webp +0 -0
  337. /package/.output/{public → ui}/assets/zhipuai-BPNAnxo-.svg +0 -0
  338. /package/src/{routes → backend/routes}/livez.ts +0 -0
@@ -2,12 +2,18 @@ import http from "node:http";
2
2
  import { Buffer } from "node:buffer";
3
3
  import { createHash, timingSafeEqual } from "node:crypto";
4
4
  import { isIP } from "node:net";
5
+ import {
6
+ BROWSER_RUNTIME_JSON_PLACEHOLDER,
7
+ BROWSER_RUNTIME_SCHEMA_VERSION,
8
+ type BrowserRuntimeDeployment,
9
+ } from "../lib/browserRuntimeContract";
5
10
  import {
6
11
  AGENT_INSPECTOR_BASE_PATH_META_NAME,
7
12
  AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER,
13
+ AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER,
8
14
  AGENT_INSPECTOR_PUBLIC_PORT_HEADER,
9
15
  getConfiguredBasePath,
10
- normalizeBasePath,
16
+ normalizePublicBasePath,
11
17
  stripBasePath,
12
18
  stripBasePathFromRequestPath,
13
19
  withBasePath,
@@ -18,6 +24,7 @@ import {
18
24
  MANAGED_INSTANCE_TOKEN_HEADER,
19
25
  } from "../lib/managedInstance";
20
26
  import { lookupClientByPort } from "./socketTracker";
27
+ import { loadUiAsset } from "./uiAssetServer";
21
28
 
22
29
  const IDENTITY_HEADERS = {
23
30
  pid: "x-agent-inspector-client-pid",
@@ -61,26 +68,19 @@ export type BasePathAliasRegistry = {
61
68
  function normalizedAliasCandidate(
62
69
  value: string,
63
70
  ): { ok: true; basePath: string } | { ok: false; code: "invalid" | "reserved"; error: string } {
64
- const normalized = normalizeBasePath(value);
65
- if (normalized.length === 0) {
66
- return {
67
- ok: false,
68
- code: "invalid",
69
- error: "Base Path aliases must be non-root paths such as /inspector.",
70
- };
71
- }
72
- if (!/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/u.test(normalized)) {
71
+ const normalized = normalizePublicBasePath(value);
72
+ if (normalized === null) {
73
73
  return {
74
74
  ok: false,
75
75
  code: "invalid",
76
76
  error: "Base Path aliases may contain only URL-safe path segments.",
77
77
  };
78
78
  }
79
- if (normalized.split("/").some((segment) => segment === "." || segment === "..")) {
79
+ if (normalized.length === 0) {
80
80
  return {
81
81
  ok: false,
82
82
  code: "invalid",
83
- error: "Base Path aliases cannot contain dot path segments.",
83
+ error: "Base Path aliases must be non-root paths such as /inspector.",
84
84
  };
85
85
  }
86
86
  if (
@@ -122,7 +122,7 @@ export function createBasePathAliasRegistry(
122
122
  canonicalBasePath: string,
123
123
  initialAliases: readonly string[] = [],
124
124
  ): BasePathAliasRegistry {
125
- const canonical = normalizeBasePath(canonicalBasePath);
125
+ const canonical = normalizePublicBasePath(canonicalBasePath) ?? "";
126
126
  const aliases: string[] = [];
127
127
 
128
128
  const add = (value: string): BasePathAliasMutationResult => {
@@ -278,8 +278,9 @@ function rewriteRootRelativePublicPath(
278
278
  sourceBasePath: string,
279
279
  ): string {
280
280
  if (!value.startsWith("/") || value.startsWith("//")) return value;
281
- const normalizedEffective = normalizeBasePath(effectiveBasePath);
282
- const normalizedSource = normalizeBasePath(sourceBasePath);
281
+ const normalizedEffective = normalizePublicBasePath(effectiveBasePath);
282
+ const normalizedSource = normalizePublicBasePath(sourceBasePath);
283
+ if (normalizedEffective === null || normalizedSource === null) return value;
283
284
  const suffixIndex = value.search(/[?#]/u);
284
285
  const pathname = suffixIndex === -1 ? value : value.slice(0, suffixIndex);
285
286
  const suffix = suffixIndex === -1 ? "" : value.slice(suffixIndex);
@@ -299,11 +300,15 @@ function rewriteQuotedAssetPaths(
299
300
  source: string,
300
301
  effectiveBasePath: string,
301
302
  sourceBasePath: string,
303
+ escape: (value: string, quote: string) => string,
302
304
  ): string {
303
305
  return source.replace(
304
306
  /(["'])(\/(?:[^"'/]+\/)*assets\/)/gu,
305
307
  (_match: string, quote: string, assetPrefix: string) =>
306
- `${quote}${rewriteRootRelativePublicPath(assetPrefix, effectiveBasePath, sourceBasePath)}`,
308
+ `${quote}${escape(
309
+ rewriteRootRelativePublicPath(assetPrefix, effectiveBasePath, sourceBasePath),
310
+ quote,
311
+ )}`,
307
312
  );
308
313
  }
309
314
 
@@ -312,7 +317,9 @@ export function rewriteHtmlForBasePath(
312
317
  basePath: string,
313
318
  sourceBasePath = "",
314
319
  ): string {
315
- const normalized = normalizeBasePath(basePath);
320
+ const normalized = normalizePublicBasePath(basePath);
321
+ const normalizedSource = normalizePublicBasePath(sourceBasePath);
322
+ if (normalized === null || normalizedSource === null) return html;
316
323
  const escaped = htmlAttributeEscape(normalized);
317
324
  const meta = `<meta name="${AGENT_INSPECTOR_BASE_PATH_META_NAME}" content="${escaped}">`;
318
325
  const metaPattern = new RegExp(
@@ -331,13 +338,26 @@ export function rewriteHtmlForBasePath(
331
338
  const withRewrittenAttributes = withMeta.replace(
332
339
  /\b(href|src|action)=(["'])(\/(?!\/)[^"']*)\2/giu,
333
340
  (_match: string, attribute: string, quote: string, path: string) =>
334
- `${attribute}=${quote}${rewriteRootRelativePublicPath(path, normalized, sourceBasePath)}${quote}`,
341
+ `${attribute}=${quote}${htmlAttributeEscape(
342
+ rewriteRootRelativePublicPath(path, normalized, normalizedSource),
343
+ )}${quote}`,
344
+ );
345
+ return rewriteQuotedAssetPaths(withRewrittenAttributes, normalized, normalizedSource, (value) =>
346
+ htmlAttributeEscape(value),
335
347
  );
336
- return rewriteQuotedAssetPaths(withRewrittenAttributes, normalized, sourceBasePath);
337
348
  }
338
349
 
339
- function javaScriptStringEscape(value: string): string {
340
- return value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
350
+ function javaScriptStringEscape(value: string, quote: string): string {
351
+ const escaped = value
352
+ .replace(/\\/gu, "\\\\")
353
+ .replace(/\r/gu, "\\r")
354
+ .replace(/\n/gu, "\\n")
355
+ .replace(/\u2028/gu, "\\u2028")
356
+ .replace(/\u2029/gu, "\\u2029")
357
+ .replace(/&/gu, "\\u0026")
358
+ .replace(/</gu, "\\u003c")
359
+ .replace(/>/gu, "\\u003e");
360
+ return quote === "'" ? escaped.replace(/'/gu, "\\'") : escaped.replace(/"/gu, '\\"');
341
361
  }
342
362
 
343
363
  export function rewriteJavaScriptForBasePath(
@@ -345,22 +365,118 @@ export function rewriteJavaScriptForBasePath(
345
365
  basePath: string,
346
366
  sourceBasePath = "",
347
367
  ): string {
348
- const normalized = normalizeBasePath(basePath);
349
- const escaped = javaScriptStringEscape(normalized);
368
+ const normalized = normalizePublicBasePath(basePath);
369
+ const normalizedSource = normalizePublicBasePath(sourceBasePath);
370
+ if (normalized === null || normalizedSource === null) return source;
371
+ const escaped = javaScriptStringEscape(normalized, '"');
350
372
  const withRouterBasePath = source.replace(
351
373
  /\bbasepath:\s*(["'])[^"']*\1/gu,
352
374
  `basepath:"${escaped}"`,
353
375
  );
354
376
  const withDynamicPaths = withRouterBasePath.replace(
355
377
  /return(["'])(\/(?!\/)[^"']*)\1\+([A-Za-z_$][\w$]*)/gu,
356
- (_match: string, quote: string, path: string, variable: string) =>
357
- `return${quote}${rewriteRootRelativePublicPath(
358
- path,
359
- normalized,
360
- sourceBasePath,
361
- )}${quote}+${variable}`,
378
+ (_match: string, quote: string, path: string, variable: string) => {
379
+ const rewritten = rewriteRootRelativePublicPath(path, normalized, normalizedSource);
380
+ return `return${quote}${javaScriptStringEscape(rewritten, quote)}${quote}+${variable}`;
381
+ },
382
+ );
383
+ return rewriteQuotedAssetPaths(
384
+ withDynamicPaths,
385
+ normalized,
386
+ normalizedSource,
387
+ javaScriptStringEscape,
362
388
  );
363
- return rewriteQuotedAssetPaths(withDynamicPaths, normalized, sourceBasePath);
389
+ }
390
+
391
+ function publicOriginForRequest(req: http.IncomingMessage, options: IdentityProxyOptions): string {
392
+ if (options.publicOrigin !== undefined) {
393
+ try {
394
+ return new URL(options.publicOrigin).origin;
395
+ } catch {
396
+ // Invalid explicit values are rejected by the CLI; retain a safe local fallback for embedders.
397
+ }
398
+ }
399
+ return `http://${req.headers.host ?? "localhost"}`;
400
+ }
401
+
402
+ function browserRuntimeForRequest(
403
+ req: http.IncomingMessage,
404
+ options: IdentityProxyOptions,
405
+ basePath: string,
406
+ ): BrowserRuntimeDeployment {
407
+ const origin = publicOriginForRequest(req, options);
408
+ const normalized = normalizePublicBasePath(basePath) ?? "";
409
+ const baseUrl = normalized.length === 0 ? origin : `${origin}${normalized}`;
410
+ return {
411
+ schemaVersion: BROWSER_RUNTIME_SCHEMA_VERSION,
412
+ deploymentMode: "composed",
413
+ ui: { basePath: normalized.length === 0 ? "/" : normalized },
414
+ control: { baseUrl },
415
+ agent: { baseUrl },
416
+ capabilities: {
417
+ rest: true,
418
+ sse: true,
419
+ proxy: true,
420
+ mcp: true,
421
+ },
422
+ };
423
+ }
424
+
425
+ function safeInlineJson(value: unknown): string {
426
+ return JSON.stringify(value)
427
+ .replace(/&/gu, "\\u0026")
428
+ .replace(/</gu, "\\u003c")
429
+ .replace(/>/gu, "\\u003e")
430
+ .replace(/\u2028/gu, "\\u2028")
431
+ .replace(/\u2029/gu, "\\u2029");
432
+ }
433
+
434
+ function injectBrowserRuntime(html: string, runtime: BrowserRuntimeDeployment): string {
435
+ const runtimeJson = safeInlineJson(runtime);
436
+ if (html.includes(BROWSER_RUNTIME_JSON_PLACEHOLDER)) {
437
+ return html.replaceAll(BROWSER_RUNTIME_JSON_PLACEHOLDER, runtimeJson);
438
+ }
439
+ const bootstrap = `<script>window.__AGENT_INSPECTOR_RUNTIME__=${runtimeJson};</script>`;
440
+ return html.replace(/<head(\s[^>]*)?>/iu, (head) => `${head}${bootstrap}`);
441
+ }
442
+
443
+ async function tryServeUiAsset(
444
+ req: http.IncomingMessage,
445
+ res: http.ServerResponse,
446
+ options: IdentityProxyOptions,
447
+ pathname: string,
448
+ basePath: string,
449
+ sourceBasePath: string,
450
+ ): Promise<boolean> {
451
+ const method = (req.method ?? "GET").toUpperCase();
452
+ if ((method !== "GET" && method !== "HEAD") || options.uiDirectory === undefined) return false;
453
+ const asset = await loadUiAsset(options.uiDirectory, pathname);
454
+ if (asset === null) return false;
455
+ let body = asset.body;
456
+ if (asset.document) {
457
+ const rewritten = rewriteHtmlForBasePath(body.toString("utf8"), basePath, sourceBasePath);
458
+ body = Buffer.from(
459
+ injectBrowserRuntime(rewritten, browserRuntimeForRequest(req, options, basePath)),
460
+ "utf8",
461
+ );
462
+ } else if (basePath.length > 0 && asset.contentType.includes("javascript")) {
463
+ body = Buffer.from(
464
+ rewriteJavaScriptForBasePath(body.toString("utf8"), basePath, sourceBasePath),
465
+ "utf8",
466
+ );
467
+ }
468
+ const headers: Record<string, string | number> = {
469
+ ...BASE_SECURITY_HEADERS,
470
+ "cache-control": asset.cacheControl,
471
+ "content-length": body.byteLength,
472
+ "content-type": asset.contentType,
473
+ };
474
+ if (asset.document) headers["content-security-policy"] = DOCUMENT_CONTENT_SECURITY_POLICY;
475
+ res.writeHead(200, headers);
476
+ if (method === "HEAD") res.end();
477
+ else res.end(body);
478
+ req.resume();
479
+ return true;
364
480
  }
365
481
 
366
482
  export type IdentityProxyOptions = {
@@ -384,6 +500,12 @@ export type IdentityProxyOptions = {
384
500
  allowUnauthenticatedRemoteProxy?: boolean;
385
501
  /** Additional public hostnames accepted by the ingress Host boundary. */
386
502
  trustedHosts?: readonly string[];
503
+ /** Exact browser UI origins allowed to call control REST/SSE across origins. */
504
+ allowedUiOrigins?: readonly string[];
505
+ /** Independent static UI artifact directory served only by the protected ingress. */
506
+ uiDirectory?: string;
507
+ /** Explicit public origin for reverse-proxy deployments; never inferred from forwarded headers. */
508
+ publicOrigin?: string;
387
509
  /** Maximum declared or streamed request body size. */
388
510
  maxRequestBytes?: number;
389
511
  /** Deadline for receiving and forwarding one request body. */
@@ -452,7 +574,52 @@ function normalizedHostname(value: string): string | null {
452
574
  }
453
575
 
454
576
  function isLoopbackHostname(hostname: string): boolean {
455
- return hostname === "localhost" || hostname === "::1" || hostname.startsWith("127.");
577
+ if (hostname === "localhost" || hostname === "::1") return true;
578
+ if (isIP(hostname) !== 4) return false;
579
+ return hostname.split(".", 1)[0] === "127";
580
+ }
581
+
582
+ export type UiOriginAllowlist = {
583
+ origins: readonly string[];
584
+ rejected: readonly string[];
585
+ };
586
+
587
+ function parseUrl(value: string): URL | null {
588
+ try {
589
+ return new URL(value);
590
+ } catch {
591
+ return null;
592
+ }
593
+ }
594
+
595
+ export function parseUiOriginAllowlist(value: string | undefined): UiOriginAllowlist {
596
+ const origins: string[] = [];
597
+ const rejected: string[] = [];
598
+ for (const entry of (value ?? "").split(",")) {
599
+ const candidate = entry.trim();
600
+ if (candidate.length === 0) continue;
601
+ const parsed = parseUrl(candidate);
602
+ const valid =
603
+ parsed !== null &&
604
+ (parsed.protocol === "http:" || parsed.protocol === "https:") &&
605
+ parsed.username.length === 0 &&
606
+ parsed.password.length === 0 &&
607
+ parsed.pathname === "/" &&
608
+ parsed.search.length === 0 &&
609
+ parsed.hash.length === 0 &&
610
+ isLoopbackHostname(parsed.hostname.toLowerCase().replace(/^\[|\]$/gu, ""));
611
+ if (!valid || parsed === null) {
612
+ rejected.push(candidate);
613
+ continue;
614
+ }
615
+ if (!origins.includes(parsed.origin)) origins.push(parsed.origin);
616
+ }
617
+ return { origins, rejected };
618
+ }
619
+
620
+ function normalizeTrustedPublicOrigin(value: string | undefined): string | null | undefined {
621
+ if (value === undefined) return undefined;
622
+ return normalizedRequestOrigin(value.trim());
456
623
  }
457
624
 
458
625
  export function isTrustedIdentityProxyHost(
@@ -476,6 +643,111 @@ function isSafeMethod(method: string | undefined): boolean {
476
643
  return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
477
644
  }
478
645
 
646
+ const UI_CORS_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
647
+ const UI_CORS_HEADERS = ["authorization", "content-type", "last-event-id", CSRF_HEADER] as const;
648
+
649
+ function normalizedRequestOrigin(origin: string): string | null {
650
+ try {
651
+ const parsed = new URL(origin);
652
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
653
+ if (parsed.username.length > 0 || parsed.password.length > 0) return null;
654
+ if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) return null;
655
+ return parsed.origin;
656
+ } catch {
657
+ return null;
658
+ }
659
+ }
660
+
661
+ function isSameRequestOrigin(
662
+ origin: string,
663
+ host: string | undefined,
664
+ publicOrigin?: string,
665
+ ): boolean {
666
+ const parsedOrigin = normalizedRequestOrigin(origin);
667
+ if (parsedOrigin === null) return false;
668
+ const expected = normalizedRequestOrigin(publicOrigin ?? `http://${host ?? "localhost"}`);
669
+ return expected !== null && parsedOrigin === expected;
670
+ }
671
+
672
+ function matchedUiOrigin(
673
+ origin: string | undefined,
674
+ allowedUiOrigins: readonly string[] | undefined,
675
+ ): string | null {
676
+ if (origin === undefined) return null;
677
+ return (allowedUiOrigins ?? []).find((candidate) => candidate === origin) ?? null;
678
+ }
679
+
680
+ function isUiControlPath(pathname: string): boolean {
681
+ if (pathname === "/livez" || pathname === "/readyz") return true;
682
+ if (pathname === "/api/mcp") return false;
683
+ return pathname === "/api" || pathname.startsWith("/api/");
684
+ }
685
+
686
+ function isModelProxyPath(pathname: string): boolean {
687
+ return pathname === "/proxy" || pathname.startsWith("/proxy/");
688
+ }
689
+
690
+ function uiCorsHeaders(origin: string | null): Record<string, string> {
691
+ if (origin === null) return {};
692
+ return {
693
+ "access-control-allow-origin": origin,
694
+ "access-control-expose-headers": "Content-Disposition",
695
+ vary: "Origin",
696
+ };
697
+ }
698
+
699
+ function appendVaryHeader(existing: string | string[] | undefined, name: string): string {
700
+ const values = (Array.isArray(existing) ? existing.join(",") : (existing ?? ""))
701
+ .split(",")
702
+ .map((value) => value.trim())
703
+ .filter((value) => value.length > 0);
704
+ if (!values.some((value) => value.toLowerCase() === name.toLowerCase())) values.push(name);
705
+ return values.join(", ");
706
+ }
707
+
708
+ function requestedCorsHeaders(value: string | undefined): readonly string[] {
709
+ return (value ?? "")
710
+ .split(",")
711
+ .map((header) => header.trim().toLowerCase())
712
+ .filter((header) => header.length > 0);
713
+ }
714
+
715
+ function handleUiCorsPreflight(
716
+ req: http.IncomingMessage,
717
+ res: http.ServerResponse,
718
+ origin: string,
719
+ ): void {
720
+ const requestedMethod = firstHeader(req.headers["access-control-request-method"])?.toUpperCase();
721
+ const requestedHeaders = requestedCorsHeaders(
722
+ firstHeader(req.headers["access-control-request-headers"]),
723
+ );
724
+ const methodAllowed =
725
+ requestedMethod !== undefined && UI_CORS_METHODS.some((method) => method === requestedMethod);
726
+ const headersAllowed = requestedHeaders.every((header) =>
727
+ UI_CORS_HEADERS.some((allowedHeader) => allowedHeader === header),
728
+ );
729
+ if (!methodAllowed || !headersAllowed) {
730
+ res.writeHead(403, {
731
+ "content-type": "application/json; charset=utf-8",
732
+ "cache-control": "no-store",
733
+ ...uiCorsHeaders(origin),
734
+ });
735
+ res.end(JSON.stringify({ error: "CORS preflight is not allowed for this request." }));
736
+ req.resume();
737
+ return;
738
+ }
739
+ res.writeHead(204, {
740
+ "cache-control": "no-store",
741
+ "access-control-allow-origin": origin,
742
+ "access-control-allow-methods": UI_CORS_METHODS.join(", "),
743
+ "access-control-allow-headers": UI_CORS_HEADERS.join(", "),
744
+ "access-control-max-age": "600",
745
+ vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
746
+ });
747
+ res.end();
748
+ req.resume();
749
+ }
750
+
479
751
  function isPublicHealthPath(pathname: string): boolean {
480
752
  return pathname === "/livez" || pathname === "/readyz" || pathname === "/api/health";
481
753
  }
@@ -503,23 +775,35 @@ export function isBrowserMutationAllowed(input: {
503
775
  origin?: string;
504
776
  secFetchSite?: string;
505
777
  csrfToken?: string;
778
+ allowedUiOrigins?: readonly string[];
779
+ publicOrigin?: string;
506
780
  }): boolean {
507
781
  if (isSafeMethod(input.method)) return true;
508
782
  const pathname = stripBasePath(
509
783
  new URL(input.path, "http://localhost").pathname,
510
784
  input.basePath ?? getConfiguredBasePath(process.env),
511
785
  );
512
- if (pathname === "/proxy" || pathname.startsWith("/proxy/")) return true;
513
-
514
786
  const fetchSite = input.secFetchSite?.toLowerCase();
515
787
  const browserRequest = input.origin !== undefined || fetchSite !== undefined;
516
788
  if (!browserRequest) return true;
517
- if (fetchSite !== undefined && fetchSite !== "same-origin") return false;
789
+ if (isModelProxyPath(pathname)) {
790
+ if (fetchSite !== undefined && fetchSite !== "same-origin") return false;
791
+ if (
792
+ input.origin !== undefined &&
793
+ !isSameRequestOrigin(input.origin, input.host, input.publicOrigin)
794
+ ) {
795
+ return false;
796
+ }
797
+ return input.csrfToken === "1";
798
+ }
799
+ const allowedOrigin = matchedUiOrigin(input.origin, input.allowedUiOrigins);
800
+ if (fetchSite !== undefined && fetchSite !== "same-origin" && allowedOrigin === null)
801
+ return false;
518
802
  if (input.origin !== undefined) {
519
- try {
520
- const originHost = new URL(input.origin).host.toLowerCase();
521
- if (originHost !== input.host?.toLowerCase()) return false;
522
- } catch {
803
+ if (
804
+ !isSameRequestOrigin(input.origin, input.host, input.publicOrigin) &&
805
+ allowedOrigin === null
806
+ ) {
523
807
  return false;
524
808
  }
525
809
  }
@@ -847,6 +1131,27 @@ function isIdentityProxyControlPath(pathname: string): boolean {
847
1131
  * best-effort scan inside TanStack Start.
848
1132
  */
849
1133
  export function startIdentityProxy(options: IdentityProxyOptions): Promise<http.Server> {
1134
+ const allowedUiOrigins = parseUiOriginAllowlist(options.allowedUiOrigins?.join(","));
1135
+ if (allowedUiOrigins.rejected.length > 0) {
1136
+ return Promise.reject(
1137
+ new Error(
1138
+ `Unsafe UI origins are not accepted: ${allowedUiOrigins.rejected.join(", ")}. Only exact loopback HTTP(S) origins are supported.`,
1139
+ ),
1140
+ );
1141
+ }
1142
+ const publicOrigin = normalizeTrustedPublicOrigin(options.publicOrigin);
1143
+ if (publicOrigin === null) {
1144
+ return Promise.reject(
1145
+ new Error(
1146
+ "publicOrigin must be an exact HTTP(S) origin without credentials, path, or query.",
1147
+ ),
1148
+ );
1149
+ }
1150
+ const effectiveOptions: IdentityProxyOptions = {
1151
+ ...options,
1152
+ allowedUiOrigins: allowedUiOrigins.origins,
1153
+ publicOrigin,
1154
+ };
850
1155
  return new Promise((resolve, reject) => {
851
1156
  const server = http.createServer();
852
1157
  const aliasRegistry =
@@ -893,7 +1198,7 @@ export function startIdentityProxy(options: IdentityProxyOptions): Promise<http.
893
1198
  };
894
1199
  res.once("finish", release);
895
1200
  res.once("close", release);
896
- void handleRequest(req, res, options, aliasRegistry).catch((err: unknown) => {
1201
+ void handleRequest(req, res, effectiveOptions, aliasRegistry).catch((err: unknown) => {
897
1202
  const message = err instanceof Error ? err.message : String(err);
898
1203
  if (!res.headersSent) {
899
1204
  res.writeHead(502, { "content-type": "text/plain" });
@@ -979,6 +1284,55 @@ async function handleRequest(
979
1284
  const resolution = resolveBasePathAliasRequest(aliasRegistry, req.url ?? "/");
980
1285
  const basePath = resolution.effectiveBasePath;
981
1286
  const upstreamPath = resolution.upstreamPath;
1287
+ const upstreamPathname = new URL(upstreamPath, "http://localhost").pathname;
1288
+ const requestOrigin = req.headers.origin;
1289
+ const requestPublicOrigin = publicOriginForRequest(req, options);
1290
+ const crossOriginRequest =
1291
+ requestOrigin !== undefined &&
1292
+ !isSameRequestOrigin(requestOrigin, req.headers.host, requestPublicOrigin);
1293
+ const allowedUiOrigin = crossOriginRequest
1294
+ ? matchedUiOrigin(requestOrigin, options.allowedUiOrigins)
1295
+ : null;
1296
+
1297
+ if (crossOriginRequest && isModelProxyPath(upstreamPathname)) {
1298
+ res.writeHead(403, {
1299
+ "content-type": "application/json; charset=utf-8",
1300
+ "cache-control": "no-store",
1301
+ });
1302
+ res.end(
1303
+ JSON.stringify({ error: "Cross-origin browser access to the model proxy is forbidden." }),
1304
+ );
1305
+ req.resume();
1306
+ return;
1307
+ }
1308
+
1309
+ if (crossOriginRequest && upstreamPathname === "/api/mcp") {
1310
+ res.writeHead(403, {
1311
+ "content-type": "application/json; charset=utf-8",
1312
+ "cache-control": "no-store",
1313
+ });
1314
+ res.end(JSON.stringify({ error: "Browser CORS is not enabled for the MCP endpoint." }));
1315
+ req.resume();
1316
+ return;
1317
+ }
1318
+
1319
+ if (crossOriginRequest && isUiControlPath(upstreamPathname)) {
1320
+ if (allowedUiOrigin === null) {
1321
+ res.writeHead(403, {
1322
+ "content-type": "application/json; charset=utf-8",
1323
+ "cache-control": "no-store",
1324
+ });
1325
+ res.end(
1326
+ JSON.stringify({ error: "UI origin is not allowed by this Agent Inspector runtime." }),
1327
+ );
1328
+ req.resume();
1329
+ return;
1330
+ }
1331
+ if ((req.method ?? "GET").toUpperCase() === "OPTIONS") {
1332
+ handleUiCorsPreflight(req, res, allowedUiOrigin);
1333
+ return;
1334
+ }
1335
+ }
982
1336
 
983
1337
  if (basePath.length > 0 && requestUrl.pathname.startsWith("/assets/")) {
984
1338
  res.writeHead(307, {
@@ -999,11 +1353,14 @@ async function handleRequest(
999
1353
  origin: req.headers.origin,
1000
1354
  secFetchSite: firstHeader(req.headers["sec-fetch-site"]),
1001
1355
  csrfToken: firstHeader(req.headers[CSRF_HEADER]),
1356
+ allowedUiOrigins: options.allowedUiOrigins,
1357
+ publicOrigin: requestPublicOrigin,
1002
1358
  })
1003
1359
  ) {
1004
1360
  res.writeHead(403, {
1005
1361
  "content-type": "application/json; charset=utf-8",
1006
1362
  "cache-control": "no-store",
1363
+ ...uiCorsHeaders(allowedUiOrigin),
1007
1364
  });
1008
1365
  res.end(JSON.stringify({ error: "Browser mutation requires same-origin CSRF proof" }));
1009
1366
  req.resume();
@@ -1024,6 +1381,7 @@ async function handleRequest(
1024
1381
  res.writeHead(403, {
1025
1382
  "content-type": "application/json; charset=utf-8",
1026
1383
  "cache-control": "no-store",
1384
+ ...uiCorsHeaders(allowedUiOrigin),
1027
1385
  });
1028
1386
  res.end(
1029
1387
  JSON.stringify({
@@ -1034,6 +1392,19 @@ async function handleRequest(
1034
1392
  return;
1035
1393
  }
1036
1394
 
1395
+ if (
1396
+ await tryServeUiAsset(
1397
+ req,
1398
+ res,
1399
+ options,
1400
+ upstreamPathname,
1401
+ basePath,
1402
+ aliasRegistry.canonicalBasePath,
1403
+ )
1404
+ ) {
1405
+ return;
1406
+ }
1407
+
1037
1408
  // The connection-time lookup already populated the per-port cache; this
1038
1409
  // call now hits the cache and returns the result synchronously.
1039
1410
  const remotePort = req.socket.remotePort;
@@ -1057,6 +1428,7 @@ async function handleRequest(
1057
1428
  res.writeHead(400, {
1058
1429
  "content-type": "application/json; charset=utf-8",
1059
1430
  "cache-control": "no-store",
1431
+ ...uiCorsHeaders(allowedUiOrigin),
1060
1432
  });
1061
1433
  res.end(JSON.stringify({ error: "Invalid Content-Length header" }));
1062
1434
  req.resume();
@@ -1066,6 +1438,7 @@ async function handleRequest(
1066
1438
  res.writeHead(413, {
1067
1439
  "content-type": "application/json; charset=utf-8",
1068
1440
  "cache-control": "no-store",
1441
+ ...uiCorsHeaders(allowedUiOrigin),
1069
1442
  });
1070
1443
  res.end(JSON.stringify({ error: `Request body exceeds ${String(maxRequestBytes)} bytes` }));
1071
1444
  req.resume();
@@ -1082,10 +1455,12 @@ async function handleRequest(
1082
1455
  )
1083
1456
  continue;
1084
1457
  if (lowerName.startsWith(IDENTITY_HEADER_PREFIX)) continue;
1458
+ if (lowerName === "authorization" && !isModelProxyPath(upstreamPathname)) continue;
1085
1459
  if (value === undefined) continue;
1086
1460
  upstreamHeaders[name] = value;
1087
1461
  }
1088
1462
  upstreamHeaders[AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER] = basePath.length === 0 ? "/" : basePath;
1463
+ upstreamHeaders[AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER] = requestPublicOrigin;
1089
1464
  const publicPort = req.socket.localPort;
1090
1465
  if (publicPort !== undefined && publicPort > 0 && publicPort <= 65_535) {
1091
1466
  upstreamHeaders[AGENT_INSPECTOR_PUBLIC_PORT_HEADER] = String(publicPort);
@@ -1179,6 +1554,11 @@ async function handleRequest(
1179
1554
  for (const [name, value] of Object.entries(BASE_SECURITY_HEADERS)) {
1180
1555
  outHeaders[name] = value;
1181
1556
  }
1557
+ if (allowedUiOrigin !== null && isUiControlPath(upstreamPathname)) {
1558
+ outHeaders["access-control-allow-origin"] = allowedUiOrigin;
1559
+ outHeaders["access-control-expose-headers"] = "Content-Disposition";
1560
+ outHeaders["vary"] = appendVaryHeader(outHeaders["vary"], "Origin");
1561
+ }
1182
1562
  if (contentType?.toLowerCase().includes("text/html") === true) {
1183
1563
  outHeaders["content-security-policy"] = DOCUMENT_CONTENT_SECURITY_POLICY;
1184
1564
  }