@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.
- package/.output/{nitro.json → backend/nitro.json} +3 -3
- package/.output/backend-public/assets/index-D4LX-sw-.js +11 -0
- package/.output/cli.js +2326 -516
- package/.output/server/_libs/extend.mjs +4 -8
- package/.output/server/_libs/jszip.mjs +9 -35
- package/.output/server/_libs/react-dom.mjs +2 -3
- package/.output/server/_libs/react.mjs +1 -27
- package/.output/server/_libs/tanstack__react-router.mjs +1 -329
- package/.output/server/_libs/tanstack__router-core.mjs +42 -100
- package/.output/server/_libs/use-sync-external-store.mjs +1 -64
- package/.output/server/_libs/ws.mjs +1 -1
- package/.output/server/_ssr/index.mjs +4 -4
- package/.output/server/_ssr/{publicBasePathContext-5264L3rX.mjs → publicBasePathContext-IH2ilOfu.mjs} +52 -24
- package/.output/server/_ssr/{router-BJ_ltLim.mjs → router-D18yUq36.mjs} +19770 -19447
- package/.output/server/_ssr/{start-C6MjPjxp.mjs → start-COQu_AgD.mjs} +1 -1
- package/.output/server/_tanstack-start-manifest_v-DnbdNeun.mjs +4 -0
- package/.output/server/index.mjs +5 -145
- package/.output/{public/assets/CompareDrawer-B3_TLTJh.js → ui/assets/CompareDrawer-DAUuIJ6G.js} +1 -1
- package/.output/{public/assets/InspectorPet-Cpn6hv4P.js → ui/assets/InspectorPet-BRBVjOWI.js} +1 -1
- package/.output/ui/assets/ProxyViewerContainer-D7Sq0ctc.js +59 -0
- package/.output/{public/assets/ReplayDialog-B3EKVdJk.js → ui/assets/ReplayDialog-nDLsjOhs.js} +1 -1
- package/.output/{public/assets/RequestAnatomy-COOQjyUC.js → ui/assets/RequestAnatomy-zd4BDgL0.js} +1 -1
- package/.output/ui/assets/ResponseView-AdIXGcSn.js +2 -0
- package/.output/ui/assets/StreamingChunkSequence-CesTz8He.js +1 -0
- package/.output/{public/assets/_sessionId-zbgYl7ZJ.js → ui/assets/_sessionId-BAfzrhSU.js} +1 -1
- package/.output/{public/assets/_sessionId-BJlM3nmt.js → ui/assets/_sessionId-QnBomgPD.js} +1 -1
- package/.output/ui/assets/favicon-DYEkHSNl.svg +21 -0
- package/.output/{public/assets/index-BIK4bPTv.js → ui/assets/index-BcsdxBAd.js} +1 -1
- package/.output/ui/assets/index-BvXp42al.css +1 -0
- package/.output/{public/assets/index-D2Qw6Ri2.js → ui/assets/index-D0rtCN9V.js} +1 -1
- package/.output/ui/assets/index-DHpwr08Z.js +73 -0
- package/.output/{public/assets/index-xF_NY8Yb.js → ui/assets/index-dNuk2dsU.js} +5 -5
- package/.output/ui/assets/json-viewer-BvFHglMb.js +1 -0
- package/.output/{public/assets/jszip.min-B_rfAUCe.js → ui/assets/jszip.min-B5Z7gNZB.js} +1 -1
- package/.output/ui/index.html +229 -0
- package/.output/ui/runtime-config.js +2 -0
- package/.output/workers/logFinalizer.worker.js +979 -414
- package/.output/workers/sessionWorkerEntry.js +979 -414
- package/README.md +175 -43
- package/docs/instance-control.openapi.yaml +59 -4
- package/package.json +25 -5
- package/scripts/generate-theme-scales.mjs +3 -3
- package/src/{router.tsx → backend/router.tsx} +3 -15
- package/src/backend/routes/$.ts +26 -0
- package/src/backend/routes/__root.ts +4 -0
- package/src/{routes → backend/routes}/api/-instances.ts +3 -3
- package/src/{routes → backend/routes}/api/alerts.summary.ts +3 -3
- package/src/{routes → backend/routes}/api/alerts.ts +4 -4
- package/src/{routes → backend/routes}/api/config.paths.ts +1 -1
- package/src/{routes → backend/routes}/api/config.ts +11 -2
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.help.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.install.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.runner-presets.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.$packageId.upgrade.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.packages.ts +5 -1
- package/src/{routes → backend/routes}/api/ecosystem.recipes.$recipeId.run.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.tasks.$taskId.ts +1 -1
- package/src/{routes → backend/routes}/api/ecosystem.tasks.ts +1 -1
- package/src/{routes → backend/routes}/api/groups.$groupId.evidence.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.$groupId.sessions.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.$groupId.ts +2 -2
- package/src/{routes → backend/routes}/api/groups.ts +2 -2
- package/src/{routes → backend/routes}/api/health.ts +3 -3
- package/src/{routes → backend/routes}/api/instances.$name.connection.ts +1 -1
- package/src/{routes → backend/routes}/api/instances.$name.restart.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.start.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.stop.ts +2 -2
- package/src/{routes → backend/routes}/api/instances.$name.ts +1 -1
- package/src/{routes → backend/routes}/api/instances.ts +1 -1
- package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.promote.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.candidates.$candidateId.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.candidates.ts +1 -1
- package/src/{routes → backend/routes}/api/knowledge.project-context.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.search.ts +2 -2
- package/src/{routes → backend/routes}/api/knowledge.sessions.$sessionId.candidates.ts +3 -3
- package/src/{routes → backend/routes}/api/logs.$id.body.ts +6 -6
- package/src/{routes → backend/routes}/api/logs.$id.chunks.ts +5 -4
- package/src/{routes → backend/routes}/api/logs.$id.replay.ts +12 -11
- package/src/{routes → backend/routes}/api/logs.$id.ts +4 -3
- package/src/{routes → backend/routes}/api/logs.import.ts +3 -3
- package/src/{routes → backend/routes}/api/logs.stream.ts +11 -7
- package/src/{routes → backend/routes}/api/logs.ts +17 -9
- package/src/{routes → backend/routes}/api/mcp.ts +1 -1
- package/src/{routes → backend/routes}/api/models.ts +1 -1
- package/src/{routes → backend/routes}/api/pi-agent.ts +2 -2
- package/src/{routes → backend/routes}/api/providers.$providerId.model-metadata.ts +5 -5
- package/src/{routes → backend/routes}/api/providers.$providerId.test.log.ts +7 -7
- package/src/{routes → backend/routes}/api/providers.$providerId.ts +4 -4
- package/src/{routes → backend/routes}/api/providers.export.ts +1 -1
- package/src/{routes → backend/routes}/api/providers.import.ts +3 -3
- package/src/{routes → backend/routes}/api/providers.scan.ts +2 -2
- package/src/{routes → backend/routes}/api/providers.ts +9 -4
- package/src/{routes → backend/routes}/api/runs.$runId.evidence.ts +2 -2
- package/src/{routes → backend/routes}/api/runs.$runId.ts +2 -2
- package/src/{routes → backend/routes}/api/runs.ts +4 -4
- package/src/backend/routes/api/runtime.ts +46 -0
- package/src/{routes → backend/routes}/api/sessions.ts +3 -3
- package/src/{routes → backend/routes}/proxy/$.ts +1 -1
- package/src/{routes → backend/routes}/readyz.ts +1 -1
- package/src/{start.ts → backend/start.ts} +1 -1
- package/src/cli/alias.ts +2 -11
- package/src/cli/doctor.ts +32 -9
- package/src/cli/instance.ts +26 -13
- package/src/cli/instanceArgs.ts +30 -11
- package/src/cli/instanceControl.ts +0 -2
- package/src/cli/instanceModel.ts +9 -17
- package/src/cli/instanceRegistry.ts +0 -9
- package/src/cli/networkHints.ts +27 -14
- package/src/cli/onboard.ts +69 -2
- package/src/cli/onboardTarget.ts +89 -40
- package/src/cli/templates/codex-skill-onboard.ts +44 -14
- package/src/cli/templates/skill-onboard.ts +64 -28
- package/src/cli.ts +184 -61
- package/src/components/ProxyViewer.tsx +11 -9
- package/src/components/ProxyViewerContainer.tsx +12 -11
- package/src/components/pi-agent/PiAgentPanel.tsx +2 -2
- package/src/components/providers/ProviderCard.tsx +1 -1
- package/src/components/providers/ProviderTestResultsView.tsx +1 -1
- package/src/components/providers/SettingsDialog.tsx +159 -144
- package/src/components/proxy-viewer/ApplicationBar.tsx +2 -1
- package/src/components/proxy-viewer/LogEntry.tsx +1 -1
- package/src/components/proxy-viewer/log-formats/index.ts +1 -1
- package/src/contracts/index.ts +5 -0
- package/src/contracts/log.ts +33 -3
- package/src/knowledge/types.ts +3 -2
- package/src/lib/alertContract.ts +2 -2
- package/src/lib/apiClient.ts +30 -7
- package/src/lib/apiFormat.ts +29 -0
- package/src/lib/basePath.ts +29 -11
- package/src/lib/browserRuntimeContract.ts +124 -0
- package/src/lib/browserRuntimeResolver.ts +244 -0
- package/src/{proxy → lib}/claudeCodeStrip.ts +6 -7
- package/src/lib/codingAgentEndpoints.ts +38 -0
- package/src/lib/instanceContract.ts +27 -11
- package/src/lib/piAgentContract.ts +3 -2
- package/src/lib/publicBasePathContext.ts +40 -10
- package/src/lib/resourceLimits.ts +2 -0
- package/src/lib/runContract.ts +3 -3
- package/src/lib/sessionInfoContract.ts +3 -2
- package/src/mcp/instanceHandlers.ts +1 -0
- package/src/mcp/server.ts +19 -24
- package/src/proxy/config.ts +25 -30
- package/src/proxy/dataDir.ts +43 -7
- package/src/proxy/ecosystemTasks.ts +161 -70
- package/src/proxy/formats/registry.ts +6 -21
- package/src/proxy/handler.ts +129 -29
- package/src/proxy/identityProxy.ts +419 -39
- package/src/proxy/jsonlRecovery.ts +554 -0
- package/src/proxy/logFinalizer.ts +25 -10
- package/src/proxy/logImporter.ts +2 -1
- package/src/proxy/logIndex.ts +191 -95
- package/src/proxy/logger.ts +77 -14
- package/src/proxy/platformCommands.ts +41 -0
- package/src/proxy/privateDataPath.ts +310 -88
- package/src/proxy/rawStreamCapture.ts +194 -13
- package/src/proxy/runtimeAdmission.ts +5 -2
- package/src/proxy/runtimeHealth.ts +14 -1
- package/src/proxy/runtimeShutdown.ts +11 -1
- package/src/proxy/schemas.ts +7 -1
- package/src/proxy/sessionArchive.ts +78 -14
- package/src/proxy/sqliteLogIndex.ts +65 -2
- package/src/proxy/store.ts +141 -34
- package/src/proxy/uiAssetServer.ts +106 -0
- package/src/proxy/upstream.ts +29 -2
- package/src/ui/bootstrap.ts +167 -0
- package/src/ui/index.html +38 -0
- package/src/ui/logsStreamClient.ts +20 -0
- package/src/ui/main.tsx +13 -0
- package/src/ui/public/runtime-config.js +2 -0
- package/src/ui/router.tsx +13 -0
- package/src/ui/routes/__root.tsx +69 -0
- package/src/{routes → ui/routes}/index.tsx +2 -3
- package/src/{routes → ui/routes}/session/$sessionId.tsx +5 -11
- package/src/ui/runtimeDiscovery.ts +50 -0
- package/styles/globals.css +1 -0
- package/.output/public/assets/ProxyViewerContainer-BEAhuGOJ.js +0 -126
- package/.output/public/assets/ResponseView-DBCjkwye.js +0 -2
- package/.output/public/assets/StreamingChunkSequence-CuXhDgMo.js +0 -1
- package/.output/public/assets/index-BZo_8koU.js +0 -70
- package/.output/public/assets/index-ZWschkCh.css +0 -1
- package/.output/public/assets/json-viewer-B-Ao6pwW.js +0 -1
- package/.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs +0 -1
- package/.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs +0 -344
- package/.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs +0 -2
- package/.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs +0 -11
- package/.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs +0 -69
- package/.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs +0 -1
- package/.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs +0 -28
- package/.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs +0 -6
- package/.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs +0 -34
- package/.output/server/_libs/aria-hidden.mjs +0 -122
- package/.output/server/_libs/bail.mjs +0 -8
- package/.output/server/_libs/character-entities.mjs +0 -2130
- package/.output/server/_libs/class-variance-authority.mjs +0 -44
- package/.output/server/_libs/clsx.mjs +0 -16
- package/.output/server/_libs/comma-separated-tokens.mjs +0 -10
- package/.output/server/_libs/decode-named-character-reference+[...].mjs +0 -8
- package/.output/server/_libs/dequal.mjs +0 -27
- package/.output/server/_libs/detect-node-es.mjs +0 -1
- package/.output/server/_libs/devlop.mjs +0 -8
- package/.output/server/_libs/diff.mjs +0 -320
- package/.output/server/_libs/estree-util-is-identifier-name.mjs +0 -11
- package/.output/server/_libs/floating-ui__core.mjs +0 -725
- package/.output/server/_libs/floating-ui__dom.mjs +0 -622
- package/.output/server/_libs/floating-ui__react-dom.mjs +0 -292
- package/.output/server/_libs/floating-ui__utils.mjs +0 -320
- package/.output/server/_libs/get-nonce.mjs +0 -9
- package/.output/server/_libs/hast-util-to-jsx-runtime.mjs +0 -388
- package/.output/server/_libs/hast-util-whitespace.mjs +0 -10
- package/.output/server/_libs/html-url-attributes.mjs +0 -26
- package/.output/server/_libs/inline-style-parser.mjs +0 -142
- package/.output/server/_libs/is-plain-obj.mjs +0 -10
- package/.output/server/_libs/lucide-react.mjs +0 -1027
- package/.output/server/_libs/mdast-util-from-markdown.mjs +0 -717
- package/.output/server/_libs/mdast-util-to-hast.mjs +0 -710
- package/.output/server/_libs/mdast-util-to-string.mjs +0 -38
- package/.output/server/_libs/micromark-core-commonmark.mjs +0 -2259
- package/.output/server/_libs/micromark-factory-destination.mjs +0 -94
- package/.output/server/_libs/micromark-factory-label.mjs +0 -63
- package/.output/server/_libs/micromark-factory-space.mjs +0 -24
- package/.output/server/_libs/micromark-factory-title.mjs +0 -65
- package/.output/server/_libs/micromark-factory-whitespace.mjs +0 -22
- package/.output/server/_libs/micromark-util-character.mjs +0 -44
- package/.output/server/_libs/micromark-util-chunked.mjs +0 -36
- package/.output/server/_libs/micromark-util-classify-character+[...].mjs +0 -12
- package/.output/server/_libs/micromark-util-combine-extensions+[...].mjs +0 -41
- package/.output/server/_libs/micromark-util-decode-numeric-character-reference+[...].mjs +0 -19
- package/.output/server/_libs/micromark-util-decode-string.mjs +0 -21
- package/.output/server/_libs/micromark-util-encode.mjs +0 -1
- package/.output/server/_libs/micromark-util-html-tag-name.mjs +0 -69
- package/.output/server/_libs/micromark-util-normalize-identifier+[...].mjs +0 -6
- package/.output/server/_libs/micromark-util-resolve-all.mjs +0 -15
- package/.output/server/_libs/micromark-util-sanitize-uri.mjs +0 -41
- package/.output/server/_libs/micromark-util-subtokenize.mjs +0 -346
- package/.output/server/_libs/micromark.mjs +0 -906
- package/.output/server/_libs/property-information.mjs +0 -1209
- package/.output/server/_libs/radix-ui.mjs +0 -1
- package/.output/server/_libs/radix-ui__number.mjs +0 -6
- package/.output/server/_libs/radix-ui__primitive.mjs +0 -11
- package/.output/server/_libs/radix-ui__react-accordion.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-alert-dialog.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
- package/.output/server/_libs/radix-ui__react-aspect-ratio.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-avatar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-checkbox.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-collapsible.mjs +0 -144
- package/.output/server/_libs/radix-ui__react-collection.mjs +0 -69
- package/.output/server/_libs/radix-ui__react-compose-refs.mjs +0 -38
- package/.output/server/_libs/radix-ui__react-context-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-context.mjs +0 -64
- package/.output/server/_libs/radix-ui__react-dialog.mjs +0 -280
- package/.output/server/_libs/radix-ui__react-direction.mjs +0 -9
- package/.output/server/_libs/radix-ui__react-dropdown-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-focus-guards.mjs +0 -39
- package/.output/server/_libs/radix-ui__react-focus-scope.mjs +0 -210
- package/.output/server/_libs/radix-ui__react-form.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-hover-card.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-id.mjs +0 -14
- package/.output/server/_libs/radix-ui__react-label.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-menu.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-menubar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-popover.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-popper.mjs +0 -340
- package/.output/server/_libs/radix-ui__react-portal.mjs +0 -16
- package/.output/server/_libs/radix-ui__react-presence.mjs +0 -173
- package/.output/server/_libs/radix-ui__react-primitive.mjs +0 -42
- package/.output/server/_libs/radix-ui__react-progress.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-radio-group.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-roving-focus.mjs +0 -235
- package/.output/server/_libs/radix-ui__react-scroll-area.mjs +0 -731
- package/.output/server/_libs/radix-ui__react-select.mjs +0 -1207
- package/.output/server/_libs/radix-ui__react-separator.mjs +0 -28
- package/.output/server/_libs/radix-ui__react-slider.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-slot.mjs +0 -132
- package/.output/server/_libs/radix-ui__react-switch.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-tabs.mjs +0 -194
- package/.output/server/_libs/radix-ui__react-toast.mjs +0 -2
- package/.output/server/_libs/radix-ui__react-toggle-group.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-toggle.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-toolbar.mjs +0 -1
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -498
- package/.output/server/_libs/radix-ui__react-use-previous.mjs +0 -14
- package/.output/server/_libs/radix-ui__react-use-size.mjs +0 -39
- package/.output/server/_libs/react-markdown.mjs +0 -235
- package/.output/server/_libs/react-remove-scroll-bar.mjs +0 -82
- package/.output/server/_libs/react-remove-scroll.mjs +0 -328
- package/.output/server/_libs/react-style-singleton.mjs +0 -69
- package/.output/server/_libs/remark-parse.mjs +0 -19
- package/.output/server/_libs/remark-rehype.mjs +0 -21
- package/.output/server/_libs/space-separated-tokens.mjs +0 -6
- package/.output/server/_libs/style-to-js.mjs +0 -72
- package/.output/server/_libs/style-to-object.mjs +0 -38
- package/.output/server/_libs/swr.mjs +0 -947
- package/.output/server/_libs/tailwind-merge.mjs +0 -3255
- package/.output/server/_libs/tanstack__react-virtual.mjs +0 -130
- package/.output/server/_libs/tanstack__virtual-core.mjs +0 -1319
- package/.output/server/_libs/three.mjs +0 -26016
- package/.output/server/_libs/trim-lines.mjs +0 -41
- package/.output/server/_libs/trough.mjs +0 -85
- package/.output/server/_libs/tslib.mjs +0 -1
- package/.output/server/_libs/ungap__structured-clone.mjs +0 -212
- package/.output/server/_libs/unified.mjs +0 -661
- package/.output/server/_libs/unist-util-is.mjs +0 -100
- package/.output/server/_libs/unist-util-position.mjs +0 -27
- package/.output/server/_libs/unist-util-stringify-position.mjs +0 -27
- package/.output/server/_libs/unist-util-visit-parents.mjs +0 -82
- package/.output/server/_libs/unist-util-visit.mjs +0 -24
- package/.output/server/_libs/use-callback-ref.mjs +0 -66
- package/.output/server/_libs/use-sidecar.mjs +0 -106
- package/.output/server/_libs/vfile-message.mjs +0 -138
- package/.output/server/_libs/vfile.mjs +0 -467
- package/.output/server/_sessionId-CJ99gZFV.mjs +0 -84
- package/.output/server/_sessionId-DEjScBpK.mjs +0 -146
- package/.output/server/_ssr/CompareDrawer-DtOIMeQB.mjs +0 -1060
- package/.output/server/_ssr/InspectorPet-CXc4oCBQ.mjs +0 -938
- package/.output/server/_ssr/ProxyViewerContainer-DVPlwfkt.mjs +0 -17632
- package/.output/server/_ssr/ReplayDialog-BQEA95qC.mjs +0 -725
- package/.output/server/_ssr/RequestAnatomy-BMyd8B7Q.mjs +0 -837
- package/.output/server/_ssr/ResponseView-qrTaEIM8.mjs +0 -757
- package/.output/server/_ssr/StreamingChunkSequence-BFTL8mIy.mjs +0 -274
- package/.output/server/_ssr/index-Bcj1Hcy4.mjs +0 -135
- package/.output/server/_ssr/index-DW-M7XUG.mjs +0 -84
- package/.output/server/_ssr/json-viewer-DiI0Yjsn.mjs +0 -483
- package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +0 -4
- package/.output/server/node_modules/tslib/modules/index.js +0 -70
- package/.output/server/node_modules/tslib/modules/package.json +0 -3
- package/.output/server/node_modules/tslib/package.json +0 -47
- package/.output/server/node_modules/tslib/tslib.js +0 -484
- package/.output/server/package.json +0 -9
- package/src/routes/__root.tsx +0 -170
- /package/.output/{public → ui}/assets/agent-inspector-58K1_MsC.ico +0 -0
- /package/.output/{public → ui}/assets/alibaba-TTwafVwX.svg +0 -0
- /package/.output/{public → ui}/assets/minimax-BPMzvuL-.jpeg +0 -0
- /package/.output/{public → ui}/assets/qwen-mMn3f5ul.webp +0 -0
- /package/.output/{public → ui}/assets/zhipuai-BPNAnxo-.svg +0 -0
- /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
|
-
|
|
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 =
|
|
65
|
-
if (normalized
|
|
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.
|
|
79
|
+
if (normalized.length === 0) {
|
|
80
80
|
return {
|
|
81
81
|
ok: false,
|
|
82
82
|
code: "invalid",
|
|
83
|
-
error: "Base Path aliases
|
|
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 =
|
|
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 =
|
|
282
|
-
const normalizedSource =
|
|
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}${
|
|
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 =
|
|
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}${
|
|
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
|
-
|
|
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 =
|
|
349
|
-
const
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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,
|
|
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
|
}
|