@tonyclaw/agent-inspector 3.0.45 → 3.0.47

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 (343) 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 +8018 -5451
  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-BwZEsYfS.mjs} +19794 -19444
  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-V-8lPASC.js} +1 -1
  19. package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-D4CVnfdf.js} +1 -1
  20. package/.output/ui/assets/ProxyViewerContainer-CO3ZVHLX.js +59 -0
  21. package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-CpGyTLtt.js} +1 -1
  22. package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-BLuJL9dZ.js} +1 -1
  23. package/.output/ui/assets/ResponseView-BuSC-CH7.js +2 -0
  24. package/.output/ui/assets/StreamingChunkSequence-DeA-JJKa.js +1 -0
  25. package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-8HuHFhjo.js} +1 -1
  26. package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-Chp67th6.js} +1 -1
  27. package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
  28. package/.output/ui/assets/index-BvXp42al.css +1 -0
  29. package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-DPmKK_5d.js} +5 -5
  30. package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-atoQrlb8.js} +1 -1
  31. package/.output/ui/assets/index-nY3E1vxV.js +73 -0
  32. package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-nl9-dpZn.js} +1 -1
  33. package/.output/ui/assets/json-viewer-B9kIzqeX.js +1 -0
  34. package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B7tfyE-I.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 +182 -43
  40. package/docs/instance-control.openapi.yaml +59 -4
  41. package/package.json +40 -6
  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/backend/routes/api/-instances.ts +7 -0
  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 +36 -13
  104. package/src/cli/instanceApi.ts +213 -0
  105. package/src/cli/instanceArgs.ts +46 -11
  106. package/src/cli/instanceControl.ts +10 -2
  107. package/src/cli/instanceModel.ts +18 -17
  108. package/src/cli/instanceRegistry.ts +0 -9
  109. package/src/cli/networkHints.ts +27 -14
  110. package/src/cli/onboard.ts +69 -2
  111. package/src/cli/onboardTarget.ts +89 -40
  112. package/src/cli/rustBackendPackage.ts +450 -0
  113. package/src/cli/templates/codex-skill-onboard.ts +44 -14
  114. package/src/cli/templates/skill-onboard.ts +64 -28
  115. package/src/cli.ts +341 -75
  116. package/src/components/ProxyViewer.tsx +11 -9
  117. package/src/components/ProxyViewerContainer.tsx +12 -11
  118. package/src/components/pi-agent/PiAgentPanel.tsx +2 -2
  119. package/src/components/providers/ProviderCard.tsx +1 -1
  120. package/src/components/providers/ProviderTestResultsView.tsx +1 -1
  121. package/src/components/providers/SettingsDialog.tsx +159 -144
  122. package/src/components/proxy-viewer/ApplicationBar.tsx +2 -1
  123. package/src/components/proxy-viewer/LogEntry.tsx +1 -1
  124. package/src/components/proxy-viewer/log-formats/index.ts +1 -1
  125. package/src/contracts/index.ts +5 -0
  126. package/src/contracts/log.ts +33 -3
  127. package/src/knowledge/types.ts +3 -2
  128. package/src/lib/alertContract.ts +2 -2
  129. package/src/lib/apiClient.ts +30 -7
  130. package/src/lib/apiFormat.ts +29 -0
  131. package/src/lib/backendImplementation.ts +16 -0
  132. package/src/lib/basePath.ts +29 -11
  133. package/src/lib/browserRuntimeContract.ts +124 -0
  134. package/src/lib/browserRuntimeResolver.ts +244 -0
  135. package/src/{proxy → lib}/claudeCodeStrip.ts +6 -7
  136. package/src/lib/codingAgentEndpoints.ts +38 -0
  137. package/src/lib/instanceContract.ts +30 -11
  138. package/src/lib/managedInstance.ts +9 -1
  139. package/src/lib/piAgentContract.ts +3 -2
  140. package/src/lib/publicBasePathContext.ts +40 -10
  141. package/src/lib/resourceLimits.ts +2 -0
  142. package/src/lib/runContract.ts +3 -3
  143. package/src/lib/sessionInfoContract.ts +3 -2
  144. package/src/mcp/instanceHandlers.ts +2 -0
  145. package/src/mcp/server.ts +19 -24
  146. package/src/proxy/config.ts +25 -30
  147. package/src/proxy/dataDir.ts +43 -7
  148. package/src/proxy/ecosystemTasks.ts +161 -70
  149. package/src/proxy/formats/registry.ts +6 -21
  150. package/src/proxy/handler.ts +129 -29
  151. package/src/proxy/identityProxy.ts +567 -39
  152. package/src/proxy/jsonlRecovery.ts +554 -0
  153. package/src/proxy/logFinalizer.ts +25 -10
  154. package/src/proxy/logImporter.ts +2 -1
  155. package/src/proxy/logIndex.ts +191 -95
  156. package/src/proxy/logger.ts +77 -14
  157. package/src/proxy/platformCommands.ts +41 -0
  158. package/src/proxy/privateDataPath.ts +310 -88
  159. package/src/proxy/rawStreamCapture.ts +194 -13
  160. package/src/proxy/runtimeAdmission.ts +5 -2
  161. package/src/proxy/runtimeHealth.ts +14 -1
  162. package/src/proxy/runtimeShutdown.ts +11 -1
  163. package/src/proxy/schemas.ts +7 -1
  164. package/src/proxy/sessionArchive.ts +70 -14
  165. package/src/proxy/sqliteLogIndex.ts +65 -42
  166. package/src/proxy/store.ts +141 -34
  167. package/src/proxy/uiAssetServer.ts +106 -0
  168. package/src/proxy/upstream.ts +29 -2
  169. package/src/ui/bootstrap.ts +167 -0
  170. package/src/ui/index.html +38 -0
  171. package/src/ui/logsStreamClient.ts +20 -0
  172. package/src/ui/main.tsx +13 -0
  173. package/src/ui/public/runtime-config.js +2 -0
  174. package/src/ui/router.tsx +13 -0
  175. package/src/ui/routes/__root.tsx +69 -0
  176. package/src/{routes → ui/routes}/index.tsx +2 -3
  177. package/src/{routes → ui/routes}/session/$sessionId.tsx +5 -11
  178. package/src/ui/runtimeDiscovery.ts +50 -0
  179. package/styles/globals.css +1 -0
  180. package/.output/public/assets/ProxyViewerContainer-BEAhuGOJ.js +0 -126
  181. package/.output/public/assets/ResponseView-DBCjkwye.js +0 -2
  182. package/.output/public/assets/StreamingChunkSequence-CuXhDgMo.js +0 -1
  183. package/.output/public/assets/index-BZo_8koU.js +0 -70
  184. package/.output/public/assets/index-ZWschkCh.css +0 -1
  185. package/.output/public/assets/json-viewer-B-Ao6pwW.js +0 -1
  186. package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +0 -1
  187. package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +0 -344
  188. package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +0 -2
  189. package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +0 -2
  190. package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +0 -2
  191. package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +0 -11
  192. package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +0 -69
  193. package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +0 -1
  194. package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +0 -28
  195. package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +0 -6
  196. package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +0 -34
  197. package/.output/server/_libs/aria-hidden.mjs +0 -122
  198. package/.output/server/_libs/bail.mjs +0 -8
  199. package/.output/server/_libs/character-entities.mjs +0 -2130
  200. package/.output/server/_libs/class-variance-authority.mjs +0 -44
  201. package/.output/server/_libs/clsx.mjs +0 -16
  202. package/.output/server/_libs/comma-separated-tokens.mjs +0 -10
  203. package/.output/server/_libs/decode-named-character-reference+[...].mjs +0 -8
  204. package/.output/server/_libs/dequal.mjs +0 -27
  205. package/.output/server/_libs/detect-node-es.mjs +0 -1
  206. package/.output/server/_libs/devlop.mjs +0 -8
  207. package/.output/server/_libs/diff.mjs +0 -320
  208. package/.output/server/_libs/estree-util-is-identifier-name.mjs +0 -11
  209. package/.output/server/_libs/floating-ui__core.mjs +0 -725
  210. package/.output/server/_libs/floating-ui__dom.mjs +0 -622
  211. package/.output/server/_libs/floating-ui__react-dom.mjs +0 -292
  212. package/.output/server/_libs/floating-ui__utils.mjs +0 -320
  213. package/.output/server/_libs/get-nonce.mjs +0 -9
  214. package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
  215. package/.output/server/_libs/hast-util-whitespace.mjs +0 -10
  216. package/.output/server/_libs/html-url-attributes.mjs +0 -26
  217. package/.output/server/_libs/inline-style-parser.mjs +0 -142
  218. package/.output/server/_libs/is-plain-obj.mjs +0 -10
  219. package/.output/server/_libs/lucide-react.mjs +0 -1027
  220. package/.output/server/_libs/mdast-util-from-markdown.mjs +0 -717
  221. package/.output/server/_libs/mdast-util-to-hast.mjs +0 -710
  222. package/.output/server/_libs/mdast-util-to-string.mjs +0 -38
  223. package/.output/server/_libs/micromark-core-commonmark.mjs +0 -2259
  224. package/.output/server/_libs/micromark-factory-destination.mjs +0 -94
  225. package/.output/server/_libs/micromark-factory-label.mjs +0 -63
  226. package/.output/server/_libs/micromark-factory-space.mjs +0 -24
  227. package/.output/server/_libs/micromark-factory-title.mjs +0 -65
  228. package/.output/server/_libs/micromark-factory-whitespace.mjs +0 -22
  229. package/.output/server/_libs/micromark-util-character.mjs +0 -44
  230. package/.output/server/_libs/micromark-util-chunked.mjs +0 -36
  231. package/.output/server/_libs/micromark-util-classify-character+[...].mjs +0 -12
  232. package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
  233. package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
  234. package/.output/server/_libs/micromark-util-decode-string.mjs +0 -21
  235. package/.output/server/_libs/micromark-util-encode.mjs +0 -1
  236. package/.output/server/_libs/micromark-util-html-tag-name.mjs +0 -69
  237. package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
  238. package/.output/server/_libs/micromark-util-resolve-all.mjs +0 -15
  239. package/.output/server/_libs/micromark-util-sanitize-uri.mjs +0 -41
  240. package/.output/server/_libs/micromark-util-subtokenize.mjs +0 -346
  241. package/.output/server/_libs/micromark.mjs +0 -906
  242. package/.output/server/_libs/property-information.mjs +0 -1209
  243. package/.output/server/_libs/radix-ui.mjs +0 -1
  244. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  245. package/.output/server/_libs/radix-ui__primitive.mjs +0 -11
  246. package/.output/server/_libs/radix-ui__react-accordion.mjs +0 -1
  247. package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +0 -1
  248. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  249. package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +0 -1
  250. package/.output/server/_libs/radix-ui__react-avatar.mjs +0 -1
  251. package/.output/server/_libs/radix-ui__react-checkbox.mjs +0 -1
  252. package/.output/server/_libs/radix-ui__react-collapsible.mjs +0 -144
  253. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -69
  254. package/.output/server/_libs/radix-ui__react-compose-refs.mjs +0 -38
  255. package/.output/server/_libs/radix-ui__react-context-menu.mjs +0 -1
  256. package/.output/server/_libs/radix-ui__react-context.mjs +0 -64
  257. package/.output/server/_libs/radix-ui__react-dialog.mjs +0 -280
  258. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -9
  259. package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +0 -1
  260. package/.output/server/_libs/radix-ui__react-focus-guards.mjs +0 -39
  261. package/.output/server/_libs/radix-ui__react-focus-scope.mjs +0 -210
  262. package/.output/server/_libs/radix-ui__react-form.mjs +0 -1
  263. package/.output/server/_libs/radix-ui__react-hover-card.mjs +0 -1
  264. package/.output/server/_libs/radix-ui__react-id.mjs +0 -14
  265. package/.output/server/_libs/radix-ui__react-label.mjs +0 -1
  266. package/.output/server/_libs/radix-ui__react-menu.mjs +0 -1
  267. package/.output/server/_libs/radix-ui__react-menubar.mjs +0 -1
  268. package/.output/server/_libs/radix-ui__react-popover.mjs +0 -1
  269. package/.output/server/_libs/radix-ui__react-popper.mjs +0 -340
  270. package/.output/server/_libs/radix-ui__react-portal.mjs +0 -16
  271. package/.output/server/_libs/radix-ui__react-presence.mjs +0 -173
  272. package/.output/server/_libs/radix-ui__react-primitive.mjs +0 -42
  273. package/.output/server/_libs/radix-ui__react-progress.mjs +0 -1
  274. package/.output/server/_libs/radix-ui__react-radio-group.mjs +0 -1
  275. package/.output/server/_libs/radix-ui__react-roving-focus.mjs +0 -235
  276. package/.output/server/_libs/radix-ui__react-scroll-area.mjs +0 -731
  277. package/.output/server/_libs/radix-ui__react-select.mjs +0 -1207
  278. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -28
  279. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -1
  280. package/.output/server/_libs/radix-ui__react-slot.mjs +0 -132
  281. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -1
  282. package/.output/server/_libs/radix-ui__react-tabs.mjs +0 -194
  283. package/.output/server/_libs/radix-ui__react-toast.mjs +0 -2
  284. package/.output/server/_libs/radix-ui__react-toggle-group.mjs +0 -1
  285. package/.output/server/_libs/radix-ui__react-toggle.mjs +0 -1
  286. package/.output/server/_libs/radix-ui__react-toolbar.mjs +0 -1
  287. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -498
  288. package/.output/server/_libs/radix-ui__react-use-previous.mjs +0 -14
  289. package/.output/server/_libs/radix-ui__react-use-size.mjs +0 -39
  290. package/.output/server/_libs/react-markdown.mjs +0 -235
  291. package/.output/server/_libs/react-remove-scroll-bar.mjs +0 -82
  292. package/.output/server/_libs/react-remove-scroll.mjs +0 -328
  293. package/.output/server/_libs/react-style-singleton.mjs +0 -69
  294. package/.output/server/_libs/remark-parse.mjs +0 -19
  295. package/.output/server/_libs/remark-rehype.mjs +0 -21
  296. package/.output/server/_libs/space-separated-tokens.mjs +0 -6
  297. package/.output/server/_libs/style-to-js.mjs +0 -72
  298. package/.output/server/_libs/style-to-object.mjs +0 -38
  299. package/.output/server/_libs/swr.mjs +0 -947
  300. package/.output/server/_libs/tailwind-merge.mjs +0 -3255
  301. package/.output/server/_libs/tanstack__react-virtual.mjs +0 -130
  302. package/.output/server/_libs/tanstack__virtual-core.mjs +0 -1319
  303. package/.output/server/_libs/three.mjs +0 -26016
  304. package/.output/server/_libs/trim-lines.mjs +0 -41
  305. package/.output/server/_libs/trough.mjs +0 -85
  306. package/.output/server/_libs/tslib.mjs +0 -1
  307. package/.output/server/_libs/ungap__structured-clone.mjs +0 -212
  308. package/.output/server/_libs/unified.mjs +0 -661
  309. package/.output/server/_libs/unist-util-is.mjs +0 -100
  310. package/.output/server/_libs/unist-util-position.mjs +0 -27
  311. package/.output/server/_libs/unist-util-stringify-position.mjs +0 -27
  312. package/.output/server/_libs/unist-util-visit-parents.mjs +0 -82
  313. package/.output/server/_libs/unist-util-visit.mjs +0 -24
  314. package/.output/server/_libs/use-callback-ref.mjs +0 -66
  315. package/.output/server/_libs/use-sidecar.mjs +0 -106
  316. package/.output/server/_libs/vfile-message.mjs +0 -138
  317. package/.output/server/_libs/vfile.mjs +0 -467
  318. package/.output/server/_sessionId-CJ99gZFV.mjs +0 -84
  319. package/.output/server/_sessionId-DEjScBpK.mjs +0 -146
  320. package/.output/server/_ssr/CompareDrawer-DtOIMeQB.mjs +0 -1060
  321. package/.output/server/_ssr/InspectorPet-CXc4oCBQ.mjs +0 -938
  322. package/.output/server/_ssr/ProxyViewerContainer-DVPlwfkt.mjs +0 -17632
  323. package/.output/server/_ssr/ReplayDialog-BQEA95qC.mjs +0 -725
  324. package/.output/server/_ssr/RequestAnatomy-BMyd8B7Q.mjs +0 -837
  325. package/.output/server/_ssr/ResponseView-qrTaEIM8.mjs +0 -757
  326. package/.output/server/_ssr/StreamingChunkSequence-BFTL8mIy.mjs +0 -274
  327. package/.output/server/_ssr/index-Bcj1Hcy4.mjs +0 -135
  328. package/.output/server/_ssr/index-DW-M7XUG.mjs +0 -84
  329. package/.output/server/_ssr/json-viewer-DiI0Yjsn.mjs +0 -483
  330. package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +0 -4
  331. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  332. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  333. package/.output/server/node_modules/tslib/package.json +0 -47
  334. package/.output/server/node_modules/tslib/tslib.js +0 -484
  335. package/.output/server/package.json +0 -9
  336. package/src/routes/__root.tsx +0 -170
  337. package/src/routes/api/-instances.ts +0 -109
  338. /package/.output/{public → ui}/assets/agent-inspector-58K1_MsC.ico +0 -0
  339. /package/.output/{public → ui}/assets/alibaba-TTwafVwX.svg +0 -0
  340. /package/.output/{public → ui}/assets/minimax-BPMzvuL-.jpeg +0 -0
  341. /package/.output/{public → ui}/assets/qwen-mMn3f5ul.webp +0 -0
  342. /package/.output/{public → ui}/assets/zhipuai-BPNAnxo-.svg +0 -0
  343. /package/src/{routes → backend/routes}/livez.ts +0 -0
@@ -2,12 +2,22 @@ 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";
10
+ import {
11
+ AGENT_INSPECTOR_BACKEND_HEADER,
12
+ type BackendImplementation,
13
+ } from "../lib/backendImplementation";
5
14
  import {
6
15
  AGENT_INSPECTOR_BASE_PATH_META_NAME,
7
16
  AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER,
17
+ AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER,
8
18
  AGENT_INSPECTOR_PUBLIC_PORT_HEADER,
9
19
  getConfiguredBasePath,
10
- normalizeBasePath,
20
+ normalizePublicBasePath,
11
21
  stripBasePath,
12
22
  stripBasePathFromRequestPath,
13
23
  withBasePath,
@@ -18,6 +28,7 @@ import {
18
28
  MANAGED_INSTANCE_TOKEN_HEADER,
19
29
  } from "../lib/managedInstance";
20
30
  import { lookupClientByPort } from "./socketTracker";
31
+ import { loadUiAsset } from "./uiAssetServer";
21
32
 
22
33
  const IDENTITY_HEADERS = {
23
34
  pid: "x-agent-inspector-client-pid",
@@ -32,6 +43,8 @@ const CSRF_HEADER = "x-agent-inspector-csrf";
32
43
  const DEFAULT_MAX_REQUEST_BYTES = 64 * 1024 * 1024;
33
44
  const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
34
45
  const ALIAS_CONTROL_MAX_REQUEST_BYTES = 8 * 1024;
46
+ const INSTANCE_API_MAX_REQUEST_BYTES = 64 * 1024;
47
+ const LOCAL_CONTROL_MAX_RESPONSE_BYTES = 1024 * 1024;
35
48
 
36
49
  export const IDENTITY_PROXY_CONTROL_NAMESPACE = "/.agent-inspector";
37
50
  export const IDENTITY_PROXY_ALIAS_CONTROL_PATH = `${IDENTITY_PROXY_CONTROL_NAMESPACE}/base-path-aliases`;
@@ -61,26 +74,19 @@ export type BasePathAliasRegistry = {
61
74
  function normalizedAliasCandidate(
62
75
  value: string,
63
76
  ): { 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)) {
77
+ const normalized = normalizePublicBasePath(value);
78
+ if (normalized === null) {
73
79
  return {
74
80
  ok: false,
75
81
  code: "invalid",
76
82
  error: "Base Path aliases may contain only URL-safe path segments.",
77
83
  };
78
84
  }
79
- if (normalized.split("/").some((segment) => segment === "." || segment === "..")) {
85
+ if (normalized.length === 0) {
80
86
  return {
81
87
  ok: false,
82
88
  code: "invalid",
83
- error: "Base Path aliases cannot contain dot path segments.",
89
+ error: "Base Path aliases must be non-root paths such as /inspector.",
84
90
  };
85
91
  }
86
92
  if (
@@ -122,7 +128,7 @@ export function createBasePathAliasRegistry(
122
128
  canonicalBasePath: string,
123
129
  initialAliases: readonly string[] = [],
124
130
  ): BasePathAliasRegistry {
125
- const canonical = normalizeBasePath(canonicalBasePath);
131
+ const canonical = normalizePublicBasePath(canonicalBasePath) ?? "";
126
132
  const aliases: string[] = [];
127
133
 
128
134
  const add = (value: string): BasePathAliasMutationResult => {
@@ -278,8 +284,9 @@ function rewriteRootRelativePublicPath(
278
284
  sourceBasePath: string,
279
285
  ): string {
280
286
  if (!value.startsWith("/") || value.startsWith("//")) return value;
281
- const normalizedEffective = normalizeBasePath(effectiveBasePath);
282
- const normalizedSource = normalizeBasePath(sourceBasePath);
287
+ const normalizedEffective = normalizePublicBasePath(effectiveBasePath);
288
+ const normalizedSource = normalizePublicBasePath(sourceBasePath);
289
+ if (normalizedEffective === null || normalizedSource === null) return value;
283
290
  const suffixIndex = value.search(/[?#]/u);
284
291
  const pathname = suffixIndex === -1 ? value : value.slice(0, suffixIndex);
285
292
  const suffix = suffixIndex === -1 ? "" : value.slice(suffixIndex);
@@ -299,11 +306,15 @@ function rewriteQuotedAssetPaths(
299
306
  source: string,
300
307
  effectiveBasePath: string,
301
308
  sourceBasePath: string,
309
+ escape: (value: string, quote: string) => string,
302
310
  ): string {
303
311
  return source.replace(
304
312
  /(["'])(\/(?:[^"'/]+\/)*assets\/)/gu,
305
313
  (_match: string, quote: string, assetPrefix: string) =>
306
- `${quote}${rewriteRootRelativePublicPath(assetPrefix, effectiveBasePath, sourceBasePath)}`,
314
+ `${quote}${escape(
315
+ rewriteRootRelativePublicPath(assetPrefix, effectiveBasePath, sourceBasePath),
316
+ quote,
317
+ )}`,
307
318
  );
308
319
  }
309
320
 
@@ -312,7 +323,9 @@ export function rewriteHtmlForBasePath(
312
323
  basePath: string,
313
324
  sourceBasePath = "",
314
325
  ): string {
315
- const normalized = normalizeBasePath(basePath);
326
+ const normalized = normalizePublicBasePath(basePath);
327
+ const normalizedSource = normalizePublicBasePath(sourceBasePath);
328
+ if (normalized === null || normalizedSource === null) return html;
316
329
  const escaped = htmlAttributeEscape(normalized);
317
330
  const meta = `<meta name="${AGENT_INSPECTOR_BASE_PATH_META_NAME}" content="${escaped}">`;
318
331
  const metaPattern = new RegExp(
@@ -331,13 +344,26 @@ export function rewriteHtmlForBasePath(
331
344
  const withRewrittenAttributes = withMeta.replace(
332
345
  /\b(href|src|action)=(["'])(\/(?!\/)[^"']*)\2/giu,
333
346
  (_match: string, attribute: string, quote: string, path: string) =>
334
- `${attribute}=${quote}${rewriteRootRelativePublicPath(path, normalized, sourceBasePath)}${quote}`,
347
+ `${attribute}=${quote}${htmlAttributeEscape(
348
+ rewriteRootRelativePublicPath(path, normalized, normalizedSource),
349
+ )}${quote}`,
350
+ );
351
+ return rewriteQuotedAssetPaths(withRewrittenAttributes, normalized, normalizedSource, (value) =>
352
+ htmlAttributeEscape(value),
335
353
  );
336
- return rewriteQuotedAssetPaths(withRewrittenAttributes, normalized, sourceBasePath);
337
354
  }
338
355
 
339
- function javaScriptStringEscape(value: string): string {
340
- return value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"');
356
+ function javaScriptStringEscape(value: string, quote: string): string {
357
+ const escaped = value
358
+ .replace(/\\/gu, "\\\\")
359
+ .replace(/\r/gu, "\\r")
360
+ .replace(/\n/gu, "\\n")
361
+ .replace(/\u2028/gu, "\\u2028")
362
+ .replace(/\u2029/gu, "\\u2029")
363
+ .replace(/&/gu, "\\u0026")
364
+ .replace(/</gu, "\\u003c")
365
+ .replace(/>/gu, "\\u003e");
366
+ return quote === "'" ? escaped.replace(/'/gu, "\\'") : escaped.replace(/"/gu, '\\"');
341
367
  }
342
368
 
343
369
  export function rewriteJavaScriptForBasePath(
@@ -345,25 +371,123 @@ export function rewriteJavaScriptForBasePath(
345
371
  basePath: string,
346
372
  sourceBasePath = "",
347
373
  ): string {
348
- const normalized = normalizeBasePath(basePath);
349
- const escaped = javaScriptStringEscape(normalized);
374
+ const normalized = normalizePublicBasePath(basePath);
375
+ const normalizedSource = normalizePublicBasePath(sourceBasePath);
376
+ if (normalized === null || normalizedSource === null) return source;
377
+ const escaped = javaScriptStringEscape(normalized, '"');
350
378
  const withRouterBasePath = source.replace(
351
379
  /\bbasepath:\s*(["'])[^"']*\1/gu,
352
380
  `basepath:"${escaped}"`,
353
381
  );
354
382
  const withDynamicPaths = withRouterBasePath.replace(
355
383
  /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}`,
384
+ (_match: string, quote: string, path: string, variable: string) => {
385
+ const rewritten = rewriteRootRelativePublicPath(path, normalized, normalizedSource);
386
+ return `return${quote}${javaScriptStringEscape(rewritten, quote)}${quote}+${variable}`;
387
+ },
388
+ );
389
+ return rewriteQuotedAssetPaths(
390
+ withDynamicPaths,
391
+ normalized,
392
+ normalizedSource,
393
+ javaScriptStringEscape,
362
394
  );
363
- return rewriteQuotedAssetPaths(withDynamicPaths, normalized, sourceBasePath);
395
+ }
396
+
397
+ function publicOriginForRequest(req: http.IncomingMessage, options: IdentityProxyOptions): string {
398
+ if (options.publicOrigin !== undefined) {
399
+ try {
400
+ return new URL(options.publicOrigin).origin;
401
+ } catch {
402
+ // Invalid explicit values are rejected by the CLI; retain a safe local fallback for embedders.
403
+ }
404
+ }
405
+ return `http://${req.headers.host ?? "localhost"}`;
406
+ }
407
+
408
+ function browserRuntimeForRequest(
409
+ req: http.IncomingMessage,
410
+ options: IdentityProxyOptions,
411
+ basePath: string,
412
+ ): BrowserRuntimeDeployment {
413
+ const origin = publicOriginForRequest(req, options);
414
+ const normalized = normalizePublicBasePath(basePath) ?? "";
415
+ const baseUrl = normalized.length === 0 ? origin : `${origin}${normalized}`;
416
+ return {
417
+ schemaVersion: BROWSER_RUNTIME_SCHEMA_VERSION,
418
+ deploymentMode: "composed",
419
+ ui: { basePath: normalized.length === 0 ? "/" : normalized },
420
+ control: { baseUrl },
421
+ agent: { baseUrl },
422
+ capabilities: {
423
+ rest: true,
424
+ sse: true,
425
+ proxy: true,
426
+ mcp: true,
427
+ },
428
+ };
429
+ }
430
+
431
+ function safeInlineJson(value: unknown): string {
432
+ return JSON.stringify(value)
433
+ .replace(/&/gu, "\\u0026")
434
+ .replace(/</gu, "\\u003c")
435
+ .replace(/>/gu, "\\u003e")
436
+ .replace(/\u2028/gu, "\\u2028")
437
+ .replace(/\u2029/gu, "\\u2029");
438
+ }
439
+
440
+ function injectBrowserRuntime(html: string, runtime: BrowserRuntimeDeployment): string {
441
+ const runtimeJson = safeInlineJson(runtime);
442
+ if (html.includes(BROWSER_RUNTIME_JSON_PLACEHOLDER)) {
443
+ return html.replaceAll(BROWSER_RUNTIME_JSON_PLACEHOLDER, runtimeJson);
444
+ }
445
+ const bootstrap = `<script>window.__AGENT_INSPECTOR_RUNTIME__=${runtimeJson};</script>`;
446
+ return html.replace(/<head(\s[^>]*)?>/iu, (head) => `${head}${bootstrap}`);
447
+ }
448
+
449
+ async function tryServeUiAsset(
450
+ req: http.IncomingMessage,
451
+ res: http.ServerResponse,
452
+ options: IdentityProxyOptions,
453
+ pathname: string,
454
+ basePath: string,
455
+ sourceBasePath: string,
456
+ ): Promise<boolean> {
457
+ const method = (req.method ?? "GET").toUpperCase();
458
+ if ((method !== "GET" && method !== "HEAD") || options.uiDirectory === undefined) return false;
459
+ const asset = await loadUiAsset(options.uiDirectory, pathname);
460
+ if (asset === null) return false;
461
+ let body = asset.body;
462
+ if (asset.document) {
463
+ const rewritten = rewriteHtmlForBasePath(body.toString("utf8"), basePath, sourceBasePath);
464
+ body = Buffer.from(
465
+ injectBrowserRuntime(rewritten, browserRuntimeForRequest(req, options, basePath)),
466
+ "utf8",
467
+ );
468
+ } else if (basePath.length > 0 && asset.contentType.includes("javascript")) {
469
+ body = Buffer.from(
470
+ rewriteJavaScriptForBasePath(body.toString("utf8"), basePath, sourceBasePath),
471
+ "utf8",
472
+ );
473
+ }
474
+ const headers: Record<string, string | number> = {
475
+ ...BASE_SECURITY_HEADERS,
476
+ "cache-control": asset.cacheControl,
477
+ "content-length": body.byteLength,
478
+ "content-type": asset.contentType,
479
+ };
480
+ if (asset.document) headers["content-security-policy"] = DOCUMENT_CONTENT_SECURITY_POLICY;
481
+ res.writeHead(200, headers);
482
+ if (method === "HEAD") res.end();
483
+ else res.end(body);
484
+ req.resume();
485
+ return true;
364
486
  }
365
487
 
366
488
  export type IdentityProxyOptions = {
489
+ /** Backend child implementation supervised behind this protected ingress. */
490
+ backendImplementation?: BackendImplementation;
367
491
  /** Public port to listen on. Clients point at this. */
368
492
  listenPort: number;
369
493
  /** Interface exposed to clients. Defaults are chosen by the CLI. */
@@ -384,6 +508,12 @@ export type IdentityProxyOptions = {
384
508
  allowUnauthenticatedRemoteProxy?: boolean;
385
509
  /** Additional public hostnames accepted by the ingress Host boundary. */
386
510
  trustedHosts?: readonly string[];
511
+ /** Exact browser UI origins allowed to call control REST/SSE across origins. */
512
+ allowedUiOrigins?: readonly string[];
513
+ /** Independent static UI artifact directory served only by the protected ingress. */
514
+ uiDirectory?: string;
515
+ /** Explicit public origin for reverse-proxy deployments; never inferred from forwarded headers. */
516
+ publicOrigin?: string;
387
517
  /** Maximum declared or streamed request body size. */
388
518
  maxRequestBytes?: number;
389
519
  /** Deadline for receiving and forwarding one request body. */
@@ -392,9 +522,12 @@ export type IdentityProxyOptions = {
392
522
  aliasRegistry?: BasePathAliasRegistry;
393
523
  /** Private lifecycle identity exposed only to the owning local instance manager. */
394
524
  managedInstance?: ManagedInstanceControlOptions;
525
+ /** Optional Node-supervisor instance API retained when the selected backend is Rust. */
526
+ instanceApiHandler?: (request: Request) => Promise<Response>;
395
527
  };
396
528
 
397
529
  export type ManagedInstanceControlOptions = {
530
+ backend?: BackendImplementation;
398
531
  name: string;
399
532
  instanceId: string;
400
533
  launchId: string;
@@ -452,7 +585,52 @@ function normalizedHostname(value: string): string | null {
452
585
  }
453
586
 
454
587
  function isLoopbackHostname(hostname: string): boolean {
455
- return hostname === "localhost" || hostname === "::1" || hostname.startsWith("127.");
588
+ if (hostname === "localhost" || hostname === "::1") return true;
589
+ if (isIP(hostname) !== 4) return false;
590
+ return hostname.split(".", 1)[0] === "127";
591
+ }
592
+
593
+ export type UiOriginAllowlist = {
594
+ origins: readonly string[];
595
+ rejected: readonly string[];
596
+ };
597
+
598
+ function parseUrl(value: string): URL | null {
599
+ try {
600
+ return new URL(value);
601
+ } catch {
602
+ return null;
603
+ }
604
+ }
605
+
606
+ export function parseUiOriginAllowlist(value: string | undefined): UiOriginAllowlist {
607
+ const origins: string[] = [];
608
+ const rejected: string[] = [];
609
+ for (const entry of (value ?? "").split(",")) {
610
+ const candidate = entry.trim();
611
+ if (candidate.length === 0) continue;
612
+ const parsed = parseUrl(candidate);
613
+ const valid =
614
+ parsed !== null &&
615
+ (parsed.protocol === "http:" || parsed.protocol === "https:") &&
616
+ parsed.username.length === 0 &&
617
+ parsed.password.length === 0 &&
618
+ parsed.pathname === "/" &&
619
+ parsed.search.length === 0 &&
620
+ parsed.hash.length === 0 &&
621
+ isLoopbackHostname(parsed.hostname.toLowerCase().replace(/^\[|\]$/gu, ""));
622
+ if (!valid || parsed === null) {
623
+ rejected.push(candidate);
624
+ continue;
625
+ }
626
+ if (!origins.includes(parsed.origin)) origins.push(parsed.origin);
627
+ }
628
+ return { origins, rejected };
629
+ }
630
+
631
+ function normalizeTrustedPublicOrigin(value: string | undefined): string | null | undefined {
632
+ if (value === undefined) return undefined;
633
+ return normalizedRequestOrigin(value.trim());
456
634
  }
457
635
 
458
636
  export function isTrustedIdentityProxyHost(
@@ -476,6 +654,111 @@ function isSafeMethod(method: string | undefined): boolean {
476
654
  return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS";
477
655
  }
478
656
 
657
+ const UI_CORS_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
658
+ const UI_CORS_HEADERS = ["authorization", "content-type", "last-event-id", CSRF_HEADER] as const;
659
+
660
+ function normalizedRequestOrigin(origin: string): string | null {
661
+ try {
662
+ const parsed = new URL(origin);
663
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
664
+ if (parsed.username.length > 0 || parsed.password.length > 0) return null;
665
+ if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) return null;
666
+ return parsed.origin;
667
+ } catch {
668
+ return null;
669
+ }
670
+ }
671
+
672
+ function isSameRequestOrigin(
673
+ origin: string,
674
+ host: string | undefined,
675
+ publicOrigin?: string,
676
+ ): boolean {
677
+ const parsedOrigin = normalizedRequestOrigin(origin);
678
+ if (parsedOrigin === null) return false;
679
+ const expected = normalizedRequestOrigin(publicOrigin ?? `http://${host ?? "localhost"}`);
680
+ return expected !== null && parsedOrigin === expected;
681
+ }
682
+
683
+ function matchedUiOrigin(
684
+ origin: string | undefined,
685
+ allowedUiOrigins: readonly string[] | undefined,
686
+ ): string | null {
687
+ if (origin === undefined) return null;
688
+ return (allowedUiOrigins ?? []).find((candidate) => candidate === origin) ?? null;
689
+ }
690
+
691
+ function isUiControlPath(pathname: string): boolean {
692
+ if (pathname === "/livez" || pathname === "/readyz") return true;
693
+ if (pathname === "/api/mcp") return false;
694
+ return pathname === "/api" || pathname.startsWith("/api/");
695
+ }
696
+
697
+ function isModelProxyPath(pathname: string): boolean {
698
+ return pathname === "/proxy" || pathname.startsWith("/proxy/");
699
+ }
700
+
701
+ function uiCorsHeaders(origin: string | null): Record<string, string> {
702
+ if (origin === null) return {};
703
+ return {
704
+ "access-control-allow-origin": origin,
705
+ "access-control-expose-headers": "Content-Disposition",
706
+ vary: "Origin",
707
+ };
708
+ }
709
+
710
+ function appendVaryHeader(existing: string | string[] | undefined, name: string): string {
711
+ const values = (Array.isArray(existing) ? existing.join(",") : (existing ?? ""))
712
+ .split(",")
713
+ .map((value) => value.trim())
714
+ .filter((value) => value.length > 0);
715
+ if (!values.some((value) => value.toLowerCase() === name.toLowerCase())) values.push(name);
716
+ return values.join(", ");
717
+ }
718
+
719
+ function requestedCorsHeaders(value: string | undefined): readonly string[] {
720
+ return (value ?? "")
721
+ .split(",")
722
+ .map((header) => header.trim().toLowerCase())
723
+ .filter((header) => header.length > 0);
724
+ }
725
+
726
+ function handleUiCorsPreflight(
727
+ req: http.IncomingMessage,
728
+ res: http.ServerResponse,
729
+ origin: string,
730
+ ): void {
731
+ const requestedMethod = firstHeader(req.headers["access-control-request-method"])?.toUpperCase();
732
+ const requestedHeaders = requestedCorsHeaders(
733
+ firstHeader(req.headers["access-control-request-headers"]),
734
+ );
735
+ const methodAllowed =
736
+ requestedMethod !== undefined && UI_CORS_METHODS.some((method) => method === requestedMethod);
737
+ const headersAllowed = requestedHeaders.every((header) =>
738
+ UI_CORS_HEADERS.some((allowedHeader) => allowedHeader === header),
739
+ );
740
+ if (!methodAllowed || !headersAllowed) {
741
+ res.writeHead(403, {
742
+ "content-type": "application/json; charset=utf-8",
743
+ "cache-control": "no-store",
744
+ ...uiCorsHeaders(origin),
745
+ });
746
+ res.end(JSON.stringify({ error: "CORS preflight is not allowed for this request." }));
747
+ req.resume();
748
+ return;
749
+ }
750
+ res.writeHead(204, {
751
+ "cache-control": "no-store",
752
+ "access-control-allow-origin": origin,
753
+ "access-control-allow-methods": UI_CORS_METHODS.join(", "),
754
+ "access-control-allow-headers": UI_CORS_HEADERS.join(", "),
755
+ "access-control-max-age": "600",
756
+ vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
757
+ });
758
+ res.end();
759
+ req.resume();
760
+ }
761
+
479
762
  function isPublicHealthPath(pathname: string): boolean {
480
763
  return pathname === "/livez" || pathname === "/readyz" || pathname === "/api/health";
481
764
  }
@@ -503,23 +786,35 @@ export function isBrowserMutationAllowed(input: {
503
786
  origin?: string;
504
787
  secFetchSite?: string;
505
788
  csrfToken?: string;
789
+ allowedUiOrigins?: readonly string[];
790
+ publicOrigin?: string;
506
791
  }): boolean {
507
792
  if (isSafeMethod(input.method)) return true;
508
793
  const pathname = stripBasePath(
509
794
  new URL(input.path, "http://localhost").pathname,
510
795
  input.basePath ?? getConfiguredBasePath(process.env),
511
796
  );
512
- if (pathname === "/proxy" || pathname.startsWith("/proxy/")) return true;
513
-
514
797
  const fetchSite = input.secFetchSite?.toLowerCase();
515
798
  const browserRequest = input.origin !== undefined || fetchSite !== undefined;
516
799
  if (!browserRequest) return true;
517
- if (fetchSite !== undefined && fetchSite !== "same-origin") return false;
800
+ if (isModelProxyPath(pathname)) {
801
+ if (fetchSite !== undefined && fetchSite !== "same-origin") return false;
802
+ if (
803
+ input.origin !== undefined &&
804
+ !isSameRequestOrigin(input.origin, input.host, input.publicOrigin)
805
+ ) {
806
+ return false;
807
+ }
808
+ return input.csrfToken === "1";
809
+ }
810
+ const allowedOrigin = matchedUiOrigin(input.origin, input.allowedUiOrigins);
811
+ if (fetchSite !== undefined && fetchSite !== "same-origin" && allowedOrigin === null)
812
+ return false;
518
813
  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 {
814
+ if (
815
+ !isSameRequestOrigin(input.origin, input.host, input.publicOrigin) &&
816
+ allowedOrigin === null
817
+ ) {
523
818
  return false;
524
819
  }
525
820
  }
@@ -616,6 +911,128 @@ function readAliasControlBody(req: http.IncomingMessage): Promise<AliasControlBo
616
911
  });
617
912
  }
618
913
 
914
+ type LocalControlBodyResult =
915
+ | { ok: true; body: Buffer }
916
+ | { ok: false; status: number; error: string };
917
+
918
+ function readLocalControlBody(
919
+ req: http.IncomingMessage,
920
+ maxBytes: number,
921
+ timeoutMs: number,
922
+ ): Promise<LocalControlBodyResult> {
923
+ return new Promise((resolve) => {
924
+ const chunks: Buffer[] = [];
925
+ let receivedBytes = 0;
926
+ let settled = false;
927
+ const timer = setTimeout(() => {
928
+ finish({ ok: false, status: 408, error: "Instance request body timed out" });
929
+ req.resume();
930
+ }, timeoutMs);
931
+ const finish = (result: LocalControlBodyResult): void => {
932
+ if (settled) return;
933
+ settled = true;
934
+ clearTimeout(timer);
935
+ resolve(result);
936
+ };
937
+ req.on("data", (chunk: unknown) => {
938
+ if (settled) return;
939
+ const buffer = toBuffer(chunk);
940
+ receivedBytes += buffer.byteLength;
941
+ if (receivedBytes > maxBytes) {
942
+ finish({
943
+ ok: false,
944
+ status: 413,
945
+ error: `Instance request body exceeds ${String(maxBytes)} bytes`,
946
+ });
947
+ req.resume();
948
+ return;
949
+ }
950
+ chunks.push(buffer);
951
+ });
952
+ req.on("end", () => finish({ ok: true, body: Buffer.concat(chunks) }));
953
+ req.on("aborted", () =>
954
+ finish({ ok: false, status: 400, error: "Client aborted instance request body" }),
955
+ );
956
+ req.on("error", () =>
957
+ finish({ ok: false, status: 400, error: "Instance request body failed" }),
958
+ );
959
+ });
960
+ }
961
+
962
+ function writeLocalControlError(
963
+ res: http.ServerResponse,
964
+ status: number,
965
+ error: string,
966
+ allowedUiOrigin: string | null,
967
+ ): void {
968
+ res.writeHead(status, {
969
+ "content-type": "application/json; charset=utf-8",
970
+ "cache-control": "no-store",
971
+ ...uiCorsHeaders(allowedUiOrigin),
972
+ });
973
+ res.end(JSON.stringify({ error }));
974
+ }
975
+
976
+ async function handleLocalInstanceApi(
977
+ req: http.IncomingMessage,
978
+ res: http.ServerResponse,
979
+ handler: (request: Request) => Promise<Response>,
980
+ upstreamPath: string,
981
+ requestPublicOrigin: string,
982
+ allowedUiOrigin: string | null,
983
+ maxRequestBytes: number,
984
+ requestTimeoutMs: number,
985
+ ): Promise<void> {
986
+ const body = await readLocalControlBody(
987
+ req,
988
+ Math.min(maxRequestBytes, INSTANCE_API_MAX_REQUEST_BYTES),
989
+ requestTimeoutMs,
990
+ );
991
+ if (!body.ok) {
992
+ writeLocalControlError(res, body.status, body.error, allowedUiOrigin);
993
+ return;
994
+ }
995
+ const method = (req.method ?? "GET").toUpperCase();
996
+ const headers = new Headers();
997
+ const contentType = firstHeader(req.headers["content-type"]);
998
+ if (contentType !== undefined) headers.set("content-type", contentType);
999
+ const request = new Request(new URL(upstreamPath, requestPublicOrigin), {
1000
+ method,
1001
+ headers,
1002
+ body: method === "GET" || method === "HEAD" ? undefined : body.body.toString("utf8"),
1003
+ });
1004
+ let response: Response;
1005
+ try {
1006
+ response = await handler(request);
1007
+ } catch {
1008
+ writeLocalControlError(res, 503, "Instance supervisor request failed", allowedUiOrigin);
1009
+ return;
1010
+ }
1011
+ const responseBody = Buffer.from(await response.arrayBuffer());
1012
+ if (responseBody.byteLength > LOCAL_CONTROL_MAX_RESPONSE_BYTES) {
1013
+ writeLocalControlError(
1014
+ res,
1015
+ 502,
1016
+ "Instance supervisor response exceeded its resource limit",
1017
+ allowedUiOrigin,
1018
+ );
1019
+ return;
1020
+ }
1021
+ const responseHeaders: Record<string, string> = {
1022
+ ...uiCorsHeaders(allowedUiOrigin),
1023
+ "content-length": String(responseBody.byteLength),
1024
+ };
1025
+ response.headers.forEach((value, name) => {
1026
+ const lowerName = name.toLowerCase();
1027
+ if (!REQUEST_HOP_BY_HOP.has(lowerName) && lowerName !== "content-length") {
1028
+ responseHeaders[lowerName] = value;
1029
+ }
1030
+ });
1031
+ responseHeaders["cache-control"] = "no-store";
1032
+ res.writeHead(response.status, responseHeaders);
1033
+ res.end(responseBody);
1034
+ }
1035
+
619
1036
  function isUnknownRecord(value: unknown): value is Record<string, unknown> {
620
1037
  return typeof value === "object" && value !== null && !Array.isArray(value);
621
1038
  }
@@ -786,6 +1203,7 @@ function handleManagedInstanceControlRequest(
786
1203
  }
787
1204
  writeManagedInstanceControlResponse(res, 200, {
788
1205
  schemaVersion: 1,
1206
+ backend: managedInstance.backend ?? "typescript",
789
1207
  name: managedInstance.name,
790
1208
  instanceId: managedInstance.instanceId,
791
1209
  launchId: managedInstance.launchId,
@@ -847,6 +1265,27 @@ function isIdentityProxyControlPath(pathname: string): boolean {
847
1265
  * best-effort scan inside TanStack Start.
848
1266
  */
849
1267
  export function startIdentityProxy(options: IdentityProxyOptions): Promise<http.Server> {
1268
+ const allowedUiOrigins = parseUiOriginAllowlist(options.allowedUiOrigins?.join(","));
1269
+ if (allowedUiOrigins.rejected.length > 0) {
1270
+ return Promise.reject(
1271
+ new Error(
1272
+ `Unsafe UI origins are not accepted: ${allowedUiOrigins.rejected.join(", ")}. Only exact loopback HTTP(S) origins are supported.`,
1273
+ ),
1274
+ );
1275
+ }
1276
+ const publicOrigin = normalizeTrustedPublicOrigin(options.publicOrigin);
1277
+ if (publicOrigin === null) {
1278
+ return Promise.reject(
1279
+ new Error(
1280
+ "publicOrigin must be an exact HTTP(S) origin without credentials, path, or query.",
1281
+ ),
1282
+ );
1283
+ }
1284
+ const effectiveOptions: IdentityProxyOptions = {
1285
+ ...options,
1286
+ allowedUiOrigins: allowedUiOrigins.origins,
1287
+ publicOrigin,
1288
+ };
850
1289
  return new Promise((resolve, reject) => {
851
1290
  const server = http.createServer();
852
1291
  const aliasRegistry =
@@ -893,7 +1332,7 @@ export function startIdentityProxy(options: IdentityProxyOptions): Promise<http.
893
1332
  };
894
1333
  res.once("finish", release);
895
1334
  res.once("close", release);
896
- void handleRequest(req, res, options, aliasRegistry).catch((err: unknown) => {
1335
+ void handleRequest(req, res, effectiveOptions, aliasRegistry).catch((err: unknown) => {
897
1336
  const message = err instanceof Error ? err.message : String(err);
898
1337
  if (!res.headersSent) {
899
1338
  res.writeHead(502, { "content-type": "text/plain" });
@@ -979,6 +1418,55 @@ async function handleRequest(
979
1418
  const resolution = resolveBasePathAliasRequest(aliasRegistry, req.url ?? "/");
980
1419
  const basePath = resolution.effectiveBasePath;
981
1420
  const upstreamPath = resolution.upstreamPath;
1421
+ const upstreamPathname = new URL(upstreamPath, "http://localhost").pathname;
1422
+ const requestOrigin = req.headers.origin;
1423
+ const requestPublicOrigin = publicOriginForRequest(req, options);
1424
+ const crossOriginRequest =
1425
+ requestOrigin !== undefined &&
1426
+ !isSameRequestOrigin(requestOrigin, req.headers.host, requestPublicOrigin);
1427
+ const allowedUiOrigin = crossOriginRequest
1428
+ ? matchedUiOrigin(requestOrigin, options.allowedUiOrigins)
1429
+ : null;
1430
+
1431
+ if (crossOriginRequest && isModelProxyPath(upstreamPathname)) {
1432
+ res.writeHead(403, {
1433
+ "content-type": "application/json; charset=utf-8",
1434
+ "cache-control": "no-store",
1435
+ });
1436
+ res.end(
1437
+ JSON.stringify({ error: "Cross-origin browser access to the model proxy is forbidden." }),
1438
+ );
1439
+ req.resume();
1440
+ return;
1441
+ }
1442
+
1443
+ if (crossOriginRequest && upstreamPathname === "/api/mcp") {
1444
+ res.writeHead(403, {
1445
+ "content-type": "application/json; charset=utf-8",
1446
+ "cache-control": "no-store",
1447
+ });
1448
+ res.end(JSON.stringify({ error: "Browser CORS is not enabled for the MCP endpoint." }));
1449
+ req.resume();
1450
+ return;
1451
+ }
1452
+
1453
+ if (crossOriginRequest && isUiControlPath(upstreamPathname)) {
1454
+ if (allowedUiOrigin === null) {
1455
+ res.writeHead(403, {
1456
+ "content-type": "application/json; charset=utf-8",
1457
+ "cache-control": "no-store",
1458
+ });
1459
+ res.end(
1460
+ JSON.stringify({ error: "UI origin is not allowed by this Agent Inspector runtime." }),
1461
+ );
1462
+ req.resume();
1463
+ return;
1464
+ }
1465
+ if ((req.method ?? "GET").toUpperCase() === "OPTIONS") {
1466
+ handleUiCorsPreflight(req, res, allowedUiOrigin);
1467
+ return;
1468
+ }
1469
+ }
982
1470
 
983
1471
  if (basePath.length > 0 && requestUrl.pathname.startsWith("/assets/")) {
984
1472
  res.writeHead(307, {
@@ -999,11 +1487,14 @@ async function handleRequest(
999
1487
  origin: req.headers.origin,
1000
1488
  secFetchSite: firstHeader(req.headers["sec-fetch-site"]),
1001
1489
  csrfToken: firstHeader(req.headers[CSRF_HEADER]),
1490
+ allowedUiOrigins: options.allowedUiOrigins,
1491
+ publicOrigin: requestPublicOrigin,
1002
1492
  })
1003
1493
  ) {
1004
1494
  res.writeHead(403, {
1005
1495
  "content-type": "application/json; charset=utf-8",
1006
1496
  "cache-control": "no-store",
1497
+ ...uiCorsHeaders(allowedUiOrigin),
1007
1498
  });
1008
1499
  res.end(JSON.stringify({ error: "Browser mutation requires same-origin CSRF proof" }));
1009
1500
  req.resume();
@@ -1024,6 +1515,7 @@ async function handleRequest(
1024
1515
  res.writeHead(403, {
1025
1516
  "content-type": "application/json; charset=utf-8",
1026
1517
  "cache-control": "no-store",
1518
+ ...uiCorsHeaders(allowedUiOrigin),
1027
1519
  });
1028
1520
  res.end(
1029
1521
  JSON.stringify({
@@ -1034,6 +1526,19 @@ async function handleRequest(
1034
1526
  return;
1035
1527
  }
1036
1528
 
1529
+ if (
1530
+ await tryServeUiAsset(
1531
+ req,
1532
+ res,
1533
+ options,
1534
+ upstreamPathname,
1535
+ basePath,
1536
+ aliasRegistry.canonicalBasePath,
1537
+ )
1538
+ ) {
1539
+ return;
1540
+ }
1541
+
1037
1542
  // The connection-time lookup already populated the per-port cache; this
1038
1543
  // call now hits the cache and returns the result synchronously.
1039
1544
  const remotePort = req.socket.remotePort;
@@ -1057,6 +1562,7 @@ async function handleRequest(
1057
1562
  res.writeHead(400, {
1058
1563
  "content-type": "application/json; charset=utf-8",
1059
1564
  "cache-control": "no-store",
1565
+ ...uiCorsHeaders(allowedUiOrigin),
1060
1566
  });
1061
1567
  res.end(JSON.stringify({ error: "Invalid Content-Length header" }));
1062
1568
  req.resume();
@@ -1066,6 +1572,7 @@ async function handleRequest(
1066
1572
  res.writeHead(413, {
1067
1573
  "content-type": "application/json; charset=utf-8",
1068
1574
  "cache-control": "no-store",
1575
+ ...uiCorsHeaders(allowedUiOrigin),
1069
1576
  });
1070
1577
  res.end(JSON.stringify({ error: `Request body exceeds ${String(maxRequestBytes)} bytes` }));
1071
1578
  req.resume();
@@ -1082,10 +1589,12 @@ async function handleRequest(
1082
1589
  )
1083
1590
  continue;
1084
1591
  if (lowerName.startsWith(IDENTITY_HEADER_PREFIX)) continue;
1592
+ if (lowerName === "authorization" && !isModelProxyPath(upstreamPathname)) continue;
1085
1593
  if (value === undefined) continue;
1086
1594
  upstreamHeaders[name] = value;
1087
1595
  }
1088
1596
  upstreamHeaders[AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER] = basePath.length === 0 ? "/" : basePath;
1597
+ upstreamHeaders[AGENT_INSPECTOR_PUBLIC_ORIGIN_HEADER] = requestPublicOrigin;
1089
1598
  const publicPort = req.socket.localPort;
1090
1599
  if (publicPort !== undefined && publicPort > 0 && publicPort <= 65_535) {
1091
1600
  upstreamHeaders[AGENT_INSPECTOR_PUBLIC_PORT_HEADER] = String(publicPort);
@@ -1108,6 +1617,19 @@ async function handleRequest(
1108
1617
  options.requestTimeoutMs,
1109
1618
  DEFAULT_REQUEST_TIMEOUT_MS,
1110
1619
  );
1620
+ if (options.instanceApiHandler !== undefined && isInstanceManagementPath(upstreamPathname)) {
1621
+ await handleLocalInstanceApi(
1622
+ req,
1623
+ res,
1624
+ options.instanceApiHandler,
1625
+ upstreamPath,
1626
+ requestPublicOrigin,
1627
+ allowedUiOrigin,
1628
+ maxRequestBytes,
1629
+ requestTimeoutMs,
1630
+ );
1631
+ return;
1632
+ }
1111
1633
  const proxyReq = http.request(
1112
1634
  {
1113
1635
  hostname: options.upstreamHost,
@@ -1179,6 +1701,12 @@ async function handleRequest(
1179
1701
  for (const [name, value] of Object.entries(BASE_SECURITY_HEADERS)) {
1180
1702
  outHeaders[name] = value;
1181
1703
  }
1704
+ outHeaders[AGENT_INSPECTOR_BACKEND_HEADER] = options.backendImplementation ?? "typescript";
1705
+ if (allowedUiOrigin !== null && isUiControlPath(upstreamPathname)) {
1706
+ outHeaders["access-control-allow-origin"] = allowedUiOrigin;
1707
+ outHeaders["access-control-expose-headers"] = "Content-Disposition";
1708
+ outHeaders["vary"] = appendVaryHeader(outHeaders["vary"], "Origin");
1709
+ }
1182
1710
  if (contentType?.toLowerCase().includes("text/html") === true) {
1183
1711
  outHeaders["content-security-policy"] = DOCUMENT_CONTENT_SECURITY_POLICY;
1184
1712
  }