@tonyclaw/agent-inspector 3.0.38 → 3.0.40

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 (58) hide show
  1. package/.output/cli.js +249 -84
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BXQWabQJ.js → CompareDrawer-BEZv_kBv.js} +1 -1
  4. package/.output/public/assets/{InspectorPet-CpQEXKcr.js → InspectorPet-ChiA4cY-.js} +1 -1
  5. package/.output/public/assets/ProxyViewerContainer-DeEbzT2N.js +126 -0
  6. package/.output/public/assets/{ReplayDialog-CnYt4UKE.js → ReplayDialog-CwaZnm2I.js} +1 -1
  7. package/.output/public/assets/{RequestAnatomy-BI25al3m.js → RequestAnatomy-CF-q-pAd.js} +1 -1
  8. package/.output/public/assets/{ResponseView-D3y-I9nb.js → ResponseView-B63ljLZR.js} +1 -1
  9. package/.output/public/assets/{StreamingChunkSequence-n90dYzPp.js → StreamingChunkSequence-DLEG6OSP.js} +1 -1
  10. package/.output/public/assets/{_sessionId-CogrNhCS.js → _sessionId-CXIp9Ho8.js} +1 -1
  11. package/.output/public/assets/{_sessionId-DLbOYm3h.js → _sessionId-DeyIs1qC.js} +1 -1
  12. package/.output/public/assets/{index-BWWA1Rf6.js → index-BB7PKq5y.js} +6 -6
  13. package/.output/public/assets/index-BGsYO6pY.js +76 -0
  14. package/.output/public/assets/{index-DXGp27k9.js → index-BLB8plMU.js} +1 -1
  15. package/.output/public/assets/{index-I5zN_KrA.js → index-Cu4d716S.js} +1 -1
  16. package/.output/public/assets/{json-viewer-BoB2BfHg.js → json-viewer-DGNoUded.js} +1 -1
  17. package/.output/public/assets/{jszip.min-CAXkxQd6.js → jszip.min-BWEzkSYW.js} +1 -1
  18. package/.output/server/{_sessionId-_hORwhWH.mjs → _sessionId-C2SNEFkW.mjs} +1 -1
  19. package/.output/server/{_sessionId-DScqlEFC.mjs → _sessionId-WNO01BLN.mjs} +2 -2
  20. package/.output/server/_ssr/{CompareDrawer-BtGm5i_L.mjs → CompareDrawer-BEcSj30h.mjs} +2 -2
  21. package/.output/server/_ssr/{InspectorPet-BnN6lVqM.mjs → InspectorPet-_PYmltfg.mjs} +1 -1
  22. package/.output/server/_ssr/{ProxyViewerContainer-aduEJ_Xy.mjs → ProxyViewerContainer-BfcmY57q.mjs} +58 -23
  23. package/.output/server/_ssr/{ReplayDialog-BSGZ-TFs.mjs → ReplayDialog-jyuLGtHe.mjs} +3 -3
  24. package/.output/server/_ssr/{RequestAnatomy-DN_eSnDN.mjs → RequestAnatomy-DQbESytq.mjs} +2 -2
  25. package/.output/server/_ssr/{ResponseView-Dk9Td_HM.mjs → ResponseView--51oa9DH.mjs} +2 -2
  26. package/.output/server/_ssr/{StreamingChunkSequence-DthH8Lx4.mjs → StreamingChunkSequence-CAqCHxk1.mjs} +2 -2
  27. package/.output/server/_ssr/{index-DvVHL942.mjs → index-B1yEsvkn.mjs} +1 -1
  28. package/.output/server/_ssr/{index-CqeZgFFF.mjs → index-NOjwkApn.mjs} +2 -2
  29. package/.output/server/_ssr/index.mjs +2 -2
  30. package/.output/server/_ssr/{json-viewer-DLKA2jUq.mjs → json-viewer-CHdJjaMG.mjs} +2 -2
  31. package/.output/server/_ssr/{router-CRKEDn6n.mjs → router-C5WQ0dD2.mjs} +125 -58
  32. package/.output/server/{_tanstack-start-manifest_v-pQO9kTmV.mjs → _tanstack-start-manifest_v-DTpuiXMK.mjs} +1 -1
  33. package/.output/server/index.mjs +90 -90
  34. package/.output/workers/logFinalizer.worker.js +6 -1
  35. package/.output/workers/sessionWorkerEntry.js +6 -1
  36. package/README.md +42 -10
  37. package/package.json +1 -1
  38. package/src/cli/doctor.ts +17 -10
  39. package/src/cli/networkHints.ts +15 -8
  40. package/src/cli/onboard.ts +10 -3
  41. package/src/cli.ts +90 -56
  42. package/src/components/ProxyViewer.tsx +11 -2
  43. package/src/components/ProxyViewerContainer.tsx +2 -1
  44. package/src/components/pi-agent/PiAgentPanel.tsx +2 -1
  45. package/src/components/providers/SettingsDialog.tsx +24 -7
  46. package/src/lib/apiClient.ts +9 -2
  47. package/src/lib/basePath.ts +83 -0
  48. package/src/lib/sessionUrl.ts +4 -2
  49. package/src/mcp/loopback.ts +2 -1
  50. package/src/mcp/server.ts +6 -2
  51. package/src/proxy/identityProxy.ts +98 -6
  52. package/src/proxy/privateDataPath.ts +15 -1
  53. package/src/proxy/sessionInfo.ts +10 -3
  54. package/src/proxy/upstream.ts +3 -1
  55. package/src/router.tsx +7 -1
  56. package/src/routes/__root.tsx +9 -3
  57. package/.output/public/assets/ProxyViewerContainer-8pNJsCJ5.js +0 -126
  58. package/.output/public/assets/index-Ca0grOxW.js +0 -76
@@ -43,8 +43,13 @@ var PRIVATE_FILE_MODE = 384;
43
43
  var securedPaths = /* @__PURE__ */ new Set();
44
44
  var securedFileIdentities = /* @__PURE__ */ new Map();
45
45
  var cachedWindowsSid = null;
46
+ function windowsSystemPath(executable) {
47
+ const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
48
+ return `${systemRoot}\\System32\\${executable}`;
49
+ }
46
50
  function runWindowsCommand(executable, args) {
47
- const result = spawnSync(executable, [...args], {
51
+ const resolved = process.platform === "win32" && /^(whoami|icacls)\.exe$/iu.test(executable) ? windowsSystemPath(executable) : executable;
52
+ const result = spawnSync(resolved, [...args], {
48
53
  encoding: "utf8",
49
54
  shell: false,
50
55
  windowsHide: true
@@ -40,8 +40,13 @@ var PRIVATE_FILE_MODE = 384;
40
40
  var securedPaths = /* @__PURE__ */ new Set();
41
41
  var securedFileIdentities = /* @__PURE__ */ new Map();
42
42
  var cachedWindowsSid = null;
43
+ function windowsSystemPath(executable) {
44
+ const systemRoot = process.env["SystemRoot"] ?? "C:\\Windows";
45
+ return `${systemRoot}\\System32\\${executable}`;
46
+ }
43
47
  function runWindowsCommand(executable, args) {
44
- const result = spawnSync(executable, [...args], {
48
+ const resolved = process.platform === "win32" && /^(whoami|icacls)\.exe$/iu.test(executable) ? windowsSystemPath(executable) : executable;
49
+ const result = spawnSync(resolved, [...args], {
45
50
  encoding: "utf8",
46
51
  shell: false,
47
52
  windowsHide: true
package/README.md CHANGED
@@ -147,12 +147,44 @@ agent-inspector doctor --chrome-extension
147
147
  Then point an AI coding tool at the proxy:
148
148
 
149
149
  ```bash
150
- ANTHROPIC_BASE_URL=http://localhost:9527/proxy <tool>
150
+ ANTHROPIC_BASE_URL=http://localhost:9527/inspector/proxy <tool>
151
151
  ```
152
152
 
153
- The web UI runs at http://localhost:9527. The public proxy endpoint is also
154
- `http://localhost:9527/proxy`; internally Agent Inspector may run the app on a private helper port,
155
- but AI tools should use the public URL printed by the CLI.
153
+ The web UI runs at http://localhost:9527/inspector by default. The public proxy endpoint is
154
+ `http://localhost:9527/inspector/proxy`; internally Agent Inspector may run the app on a private
155
+ helper port, but AI tools should use the public URL printed by the CLI.
156
+
157
+ ### Base path
158
+
159
+ Agent Inspector defaults to the `/inspector` public base path:
160
+
161
+ ```bash
162
+ agent-inspector
163
+ ```
164
+
165
+ This generates prefixed public URLs for the UI, assets, REST API, MCP, sessions, and model proxy:
166
+
167
+ - Web UI: `http://localhost:9527/inspector`
168
+ - Assets: `http://localhost:9527/inspector/assets/...`
169
+ - REST API: `http://localhost:9527/inspector/api/...`
170
+ - MCP: `http://localhost:9527/inspector/api/mcp`
171
+ - Sessions: `http://localhost:9527/inspector/session/...`
172
+ - Proxy: `http://localhost:9527/inspector/proxy`
173
+
174
+ AI tools should use the prefixed proxy URL:
175
+
176
+ ```bash
177
+ ANTHROPIC_BASE_URL=http://localhost:9527/inspector/proxy <tool>
178
+ ```
179
+
180
+ To run at the domain root instead, explicitly set the base path to `/`:
181
+
182
+ ```bash
183
+ AGENT_INSPECTOR_BASE_PATH=/ agent-inspector
184
+ ```
185
+
186
+ Reverse proxies should preserve the `/inspector` prefix when forwarding public requests to Agent
187
+ Inspector.
156
188
 
157
189
  The legacy `http://localhost:9527` port is closed by default. If you need a temporary alias for
158
190
  older AI tool or MCP configs, start with `agent-inspector --legacy-port`.
@@ -163,12 +195,12 @@ Agent Inspector v3.0 changes the default public port from `9527` to `9527`. The
163
195
  opens only `9527` by default. Existing AI coding tool and MCP configs that still point at `9527`
164
196
  should be moved to `9527`, or you can temporarily start with `agent-inspector --legacy-port`.
165
197
 
166
- Use these endpoints for new configs:
198
+ Use these default endpoints for new configs:
167
199
 
168
- - Web UI: `http://localhost:9527`
169
- - Proxy: `http://localhost:9527/proxy`
170
- - OpenAI-compatible `/v1` proxy: `http://localhost:9527/proxy/v1`
171
- - MCP: `http://localhost:9527/api/mcp`
200
+ - Web UI: `http://localhost:9527/inspector`
201
+ - Proxy: `http://localhost:9527/inspector/proxy`
202
+ - OpenAI-compatible `/v1` proxy: `http://localhost:9527/inspector/proxy/v1`
203
+ - MCP: `http://localhost:9527/inspector/api/mcp`
172
204
 
173
205
  If you intentionally need the old compatibility port, start with `agent-inspector --legacy-port`.
174
206
 
@@ -178,7 +210,7 @@ For Codex-style OpenAI Responses clients, configure the proxy as an OpenAI-compa
178
210
  model_provider = "agent-inspector"
179
211
 
180
212
  [model_providers."agent-inspector"]
181
- base_url = "http://127.0.0.1:9527/proxy/v1"
213
+ base_url = "http://127.0.0.1:9527/inspector/proxy/v1"
182
214
  wire_api = "responses"
183
215
  ```
184
216
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/agent-inspector",
3
- "version": "3.0.38",
3
+ "version": "3.0.40",
4
4
  "type": "module",
5
5
  "description": "Agent observability and knowledge capture layer for AI coding tools.",
6
6
  "license": "MIT",
package/src/cli/doctor.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { isAbsolute, join, resolve } from "node:path";
3
3
  import { createConnection } from "node:net";
4
+ import { appendBasePathToOrigin, getConfiguredBasePath } from "../lib/basePath";
4
5
  import { migrateLegacyDataDirs, resolveDataDir } from "../proxy/dataDir";
5
6
 
6
7
  const DEFAULT_PORT = 9527;
@@ -55,7 +56,7 @@ export type DoctorDeps = {
55
56
  exists: (path: string) => boolean;
56
57
  listDir: (path: string) => readonly string[] | null;
57
58
  stat: (path: string) => { isDirectory: boolean; size: number } | null;
58
- fetchHealth: (port: number) => Promise<HealthProbe>;
59
+ fetchHealth: (port: number, basePath: string) => Promise<HealthProbe>;
59
60
  isPortOpen: (port: number) => Promise<boolean>;
60
61
  };
61
62
 
@@ -261,14 +262,17 @@ function checkProviderConfig(options: DoctorOptions, deps: DoctorDeps): DoctorCh
261
262
  }
262
263
 
263
264
  async function checkProxyHealth(port: number, deps: DoctorDeps): Promise<DoctorCheck> {
264
- const health = await deps.fetchHealth(port);
265
+ const basePath = getConfiguredBasePath(deps.env);
266
+ const health = await deps.fetchHealth(port, basePath);
265
267
  const url = `http://localhost:${port}`;
268
+ const proxyUrl = appendBasePathToOrigin(url, "/proxy", basePath);
269
+ const healthPath = appendBasePathToOrigin("", "/api/health", basePath);
266
270
  if (health.ok) {
267
271
  return check(
268
272
  "Proxy health",
269
273
  "pass",
270
- `agent-inspector is healthy at ${url}.`,
271
- `Proxy URL: ${url}/proxy`,
274
+ `agent-inspector is healthy at ${appendBasePathToOrigin(url, "/", basePath)}.`,
275
+ `Proxy URL: ${proxyUrl}`,
272
276
  );
273
277
  }
274
278
 
@@ -277,7 +281,7 @@ async function checkProxyHealth(port: number, deps: DoctorDeps): Promise<DoctorC
277
281
  return check(
278
282
  "Proxy health",
279
283
  "fail",
280
- `Port ${port} is accepting connections, but /api/health is not healthy${suffix}.`,
284
+ `Port ${port} is accepting connections, but ${healthPath} is not healthy${suffix}.`,
281
285
  "Stop that process, choose --port <n>, or start agent-inspector with --force-restart.",
282
286
  );
283
287
  }
@@ -583,14 +587,17 @@ export function doctorHelp(): string {
583
587
  ].join("\n");
584
588
  }
585
589
 
586
- async function fetchHealth(port: number): Promise<HealthProbe> {
590
+ async function fetchHealth(port: number, basePath: string): Promise<HealthProbe> {
587
591
  const controller = new AbortController();
588
592
  const timeout = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);
589
593
  try {
590
- const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
591
- cache: "no-store",
592
- signal: controller.signal,
593
- });
594
+ const response = await fetch(
595
+ appendBasePathToOrigin(`http://127.0.0.1:${port}`, "/api/health", basePath),
596
+ {
597
+ cache: "no-store",
598
+ signal: controller.signal,
599
+ },
600
+ );
594
601
  return { ok: response.ok, status: response.status };
595
602
  } catch {
596
603
  return { ok: false, status: null };
@@ -1,4 +1,5 @@
1
1
  import { networkInterfaces } from "node:os";
2
+ import { appendBasePathToOrigin, normalizeBasePath } from "../lib/basePath";
2
3
 
3
4
  export type NetworkInterfaceMap = ReturnType<typeof networkInterfaces>;
4
5
 
@@ -107,24 +108,30 @@ export function networkUrlsForBindHost(
107
108
  return [urlForHost(port, host)];
108
109
  }
109
110
 
111
+ function accessUrl(origin: string, path: string, basePath: string): string {
112
+ return appendBasePathToOrigin(origin, path, basePath);
113
+ }
114
+
110
115
  export function formatAccessHintLines(
111
116
  port: number,
112
117
  networkUrls: readonly string[],
113
118
  host: BindHost = undefined,
119
+ basePath = "",
114
120
  ): string[] {
121
+ const normalizedBasePath = normalizeBasePath(basePath);
115
122
  const localUrl = localUrlForPort(port);
116
123
  const hostLabel = isWildcardHost(host) ? "all interfaces (0.0.0.0)" : (trimHost(host) ?? "");
117
124
  const lines = ["Access URLs:", ` Bind host: ${hostLabel}`];
118
125
 
119
126
  if (isLoopbackHost(host) || isWildcardHost(host)) {
120
- lines.push(` Local UI: ${localUrl}`);
121
- lines.push(` Local proxy: ${localUrl}/proxy`);
122
- lines.push(` Local MCP: ${localUrl}/api/mcp`);
127
+ lines.push(` Local UI: ${accessUrl(localUrl, "/", normalizedBasePath)}`);
128
+ lines.push(` Local proxy: ${accessUrl(localUrl, "/proxy", normalizedBasePath)}`);
129
+ lines.push(` Local MCP: ${accessUrl(localUrl, "/api/mcp", normalizedBasePath)}`);
123
130
  } else {
124
131
  const boundUrl = urlForHost(port, host);
125
- lines.push(` Bound UI: ${boundUrl}`);
126
- lines.push(` Bound proxy: ${boundUrl}/proxy`);
127
- lines.push(` Bound MCP: ${boundUrl}/api/mcp`);
132
+ lines.push(` Bound UI: ${accessUrl(boundUrl, "/", normalizedBasePath)}`);
133
+ lines.push(` Bound proxy: ${accessUrl(boundUrl, "/proxy", normalizedBasePath)}`);
134
+ lines.push(` Bound MCP: ${accessUrl(boundUrl, "/api/mcp", normalizedBasePath)}`);
128
135
  lines.push(" Localhost: may not work because the server is bound to a specific host");
129
136
  }
130
137
 
@@ -134,8 +141,8 @@ export function formatAccessHintLines(
134
141
  } else if (firstNetworkUrl === undefined) {
135
142
  lines.push(" Network: no non-loopback IPv4 address detected");
136
143
  } else {
137
- lines.push(` Network UI: ${firstNetworkUrl}`);
138
- lines.push(` Network proxy: ${firstNetworkUrl}/proxy`);
144
+ lines.push(` Network UI: ${accessUrl(firstNetworkUrl, "/", normalizedBasePath)}`);
145
+ lines.push(` Network proxy: ${accessUrl(firstNetworkUrl, "/proxy", normalizedBasePath)}`);
139
146
  const moreUrls = networkUrls.slice(1);
140
147
  if (moreUrls.length > 0) {
141
148
  lines.push(` Other IPs: ${moreUrls.join(", ")}`);
@@ -24,6 +24,7 @@ import { homedir } from "node:os";
24
24
  import { dirname, join } from "node:path";
25
25
  import { fileURLToPath } from "node:url";
26
26
 
27
+ import { appendBasePathToOrigin, getConfiguredBasePath } from "../lib/basePath.js";
27
28
  import { detectAll, detectFirst } from "./detect-tools.js";
28
29
  import { renderCommandOnboard } from "./templates/command-onboard.js";
29
30
  import {
@@ -36,7 +37,8 @@ const __filename = fileURLToPath(import.meta.url);
36
37
  const __dirname = dirname(__filename);
37
38
 
38
39
  const DEFAULT_PORT = 9527;
39
- const DEFAULT_MCP_URL = `http://localhost:${DEFAULT_PORT}/api/mcp`;
40
+ const DEFAULT_ORIGIN = `http://localhost:${DEFAULT_PORT}`;
41
+ const DEFAULT_MCP_URL = `${DEFAULT_ORIGIN}/api/mcp`;
40
42
  const LEGACY_MCP_URL = "http://localhost:9527/api/mcp";
41
43
  const SKILL_DIR_NAME = "agent-inspector-onboard";
42
44
  const SKILL_FILE_NAME = "SKILL.md";
@@ -181,6 +183,11 @@ function actionForStatus(label: string, state: GeneratedFileState): string {
181
183
  }
182
184
 
183
185
  function parseFlags(argv: readonly string[]): OnboardFlags {
186
+ const defaultMcpUrl = appendBasePathToOrigin(
187
+ DEFAULT_ORIGIN,
188
+ "/api/mcp",
189
+ getConfiguredBasePath(process.env),
190
+ );
184
191
  const flags: OnboardFlags = {
185
192
  force: false,
186
193
  dryRun: false,
@@ -192,12 +199,12 @@ function parseFlags(argv: readonly string[]): OnboardFlags {
192
199
  opencodeOnly: false,
193
200
  opencodeConfig: null,
194
201
  opencodeTransport: "local",
195
- opencodeMcpUrl: DEFAULT_MCP_URL,
202
+ opencodeMcpUrl: defaultMcpUrl,
196
203
  mimo: false,
197
204
  mimoOnly: false,
198
205
  mimoConfig: null,
199
206
  mimoTransport: "local",
200
- mimoMcpUrl: DEFAULT_MCP_URL,
207
+ mimoMcpUrl: defaultMcpUrl,
201
208
  uninstall: false,
202
209
  status: false,
203
210
  json: false,
package/src/cli.ts CHANGED
@@ -6,6 +6,11 @@ import { fileURLToPath } from "node:url";
6
6
  import { dirname, join, resolve as resolvePath } from "node:path";
7
7
  import { existsSync } from "node:fs";
8
8
  import type { Readable, Writable } from "node:stream";
9
+ import {
10
+ AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH_ENV,
11
+ appendBasePathToOrigin,
12
+ getConfiguredBasePath,
13
+ } from "./lib/basePath.js";
9
14
  import {
10
15
  formatAccessHintLines,
11
16
  isLoopbackHost,
@@ -74,14 +79,17 @@ await runStart(process.argv.slice(2));
74
79
  // Legacy `start` behavior — start the proxy on the configured port. Extracted
75
80
  // into a function so the router above can keep the top-level flow readable.
76
81
  // -----------------------------------------------------------------------------
77
- async function isInspectorHealthy(port: number, host?: string): Promise<boolean> {
82
+ async function isInspectorHealthy(port: number, host?: string, basePath = ""): Promise<boolean> {
78
83
  const controller = new AbortController();
79
84
  const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
80
85
  try {
81
- const response = await fetch(`${urlForHost(port, probeHostForBindHost(host))}/api/health`, {
82
- cache: "no-store",
83
- signal: controller.signal,
84
- });
86
+ const response = await fetch(
87
+ appendBasePathToOrigin(urlForHost(port, probeHostForBindHost(host)), "/api/health", basePath),
88
+ {
89
+ cache: "no-store",
90
+ signal: controller.signal,
91
+ },
92
+ );
85
93
  return response.ok;
86
94
  } catch {
87
95
  return false;
@@ -90,14 +98,21 @@ async function isInspectorHealthy(port: number, host?: string): Promise<boolean>
90
98
  }
91
99
  }
92
100
 
93
- async function getRunningCaptureMode(port: number, host?: string): Promise<CaptureMode | null> {
101
+ async function getRunningCaptureMode(
102
+ port: number,
103
+ host?: string,
104
+ basePath = "",
105
+ ): Promise<CaptureMode | null> {
94
106
  const controller = new AbortController();
95
107
  const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
96
108
  try {
97
- const response = await fetch(`${urlForHost(port, probeHostForBindHost(host))}/api/config`, {
98
- cache: "no-store",
99
- signal: controller.signal,
100
- });
109
+ const response = await fetch(
110
+ appendBasePathToOrigin(urlForHost(port, probeHostForBindHost(host)), "/api/config", basePath),
111
+ {
112
+ cache: "no-store",
113
+ signal: controller.signal,
114
+ },
115
+ );
101
116
  if (!response.ok) return null;
102
117
  const raw: unknown = await response.json();
103
118
  return readCaptureMode(raw);
@@ -134,10 +149,11 @@ async function waitForInspectorHealthy(
134
149
  port: number,
135
150
  timeoutMs: number,
136
151
  host?: string,
152
+ basePath = "",
137
153
  ): Promise<boolean> {
138
154
  const start = Date.now();
139
155
  while (Date.now() - start < timeoutMs) {
140
- if (await isInspectorHealthy(port, host)) return true;
156
+ if (await isInspectorHealthy(port, host, basePath)) return true;
141
157
  await sleep(250);
142
158
  }
143
159
  return false;
@@ -233,9 +249,9 @@ function readCaptureMode(raw: unknown): CaptureMode | null {
233
249
  return typeof value === "string" ? parseCaptureMode(value) : null;
234
250
  }
235
251
 
236
- function printAccessHints(port: number, host?: string): void {
252
+ function printAccessHints(port: number, host?: string, basePath = ""): void {
237
253
  const networkUrls = networkUrlsForBindHost(port, host);
238
- for (const line of formatAccessHintLines(port, networkUrls, host)) {
254
+ for (const line of formatAccessHintLines(port, networkUrls, host, basePath)) {
239
255
  console.log(line);
240
256
  }
241
257
  if (!isLoopbackHost(host)) {
@@ -539,13 +555,29 @@ async function runStart(args: string[]): Promise<void> {
539
555
  const upstreamHost = enableIdentityProxy ? DEFAULT_BIND_HOST : host;
540
556
  process.env["PORT"] = String(upstreamPort);
541
557
 
558
+ const basePath = getConfiguredBasePath(process.env);
542
559
  const url = urlForHost(port, host);
543
-
544
- if (!forceRestart && (await isInspectorHealthy(port, host))) {
545
- console.log(`agent-inspector is already running at ${url}`);
546
- printAccessHints(port, host);
560
+ const uiUrl = appendBasePathToOrigin(url, "/", basePath);
561
+ const proxyUrl = appendBasePathToOrigin(url, "/proxy", basePath);
562
+ const proxyMessagesUrl = appendBasePathToOrigin(url, "/proxy/v1/messages", basePath);
563
+ const legacyUrl =
564
+ legacyAliasPort === null
565
+ ? null
566
+ : appendBasePathToOrigin(localUrlForPort(legacyAliasPort), "/", basePath);
567
+ const legacyProxyUrl =
568
+ legacyAliasPort === null
569
+ ? null
570
+ : appendBasePathToOrigin(localUrlForPort(legacyAliasPort), "/proxy", basePath);
571
+ const legacyMcpUrl =
572
+ legacyAliasPort === null
573
+ ? null
574
+ : appendBasePathToOrigin(localUrlForPort(legacyAliasPort), "/api/mcp", basePath);
575
+
576
+ if (!forceRestart && (await isInspectorHealthy(port, host, basePath))) {
577
+ console.log(`agent-inspector is already running at ${uiUrl}`);
578
+ printAccessHints(port, host, basePath);
547
579
  if (captureModeWasSpecified) {
548
- const runningMode = await getRunningCaptureMode(port, host);
580
+ const runningMode = await getRunningCaptureMode(port, host, basePath);
549
581
  if (runningMode !== null && runningMode !== captureMode) {
550
582
  console.log(`Existing instance capture mode is ${runningMode}; requested ${captureMode}.`);
551
583
  console.log(`Use --force-restart to restart with ${captureMode} mode.`);
@@ -553,7 +585,7 @@ async function runStart(args: string[]): Promise<void> {
553
585
  }
554
586
  console.log(`Use --force-restart to restart the existing instance.`);
555
587
  if (open && openWasSpecified) {
556
- openBrowser(url);
588
+ openBrowser(uiUrl);
557
589
  }
558
590
  return;
559
591
  }
@@ -605,46 +637,44 @@ async function runStart(args: string[]): Promise<void> {
605
637
  });
606
638
  supervisorProcess.unref();
607
639
 
608
- if (await waitForInspectorHealthy(port, 10000, host)) {
609
- console.log(`agent-inspector background server is ready at ${url}`);
610
- printAccessHints(port, host);
611
- if (legacyAliasPort !== null) {
612
- console.log(` Legacy proxy: http://localhost:${legacyAliasPort}/proxy`);
613
- console.log(` Legacy MCP: http://localhost:${legacyAliasPort}/api/mcp`);
640
+ if (await waitForInspectorHealthy(port, 10000, host, basePath)) {
641
+ console.log(`agent-inspector background server is ready at ${uiUrl}`);
642
+ printAccessHints(port, host, basePath);
643
+ if (legacyProxyUrl !== null && legacyMcpUrl !== null) {
644
+ console.log(` Legacy proxy: ${legacyProxyUrl}`);
645
+ console.log(` Legacy MCP: ${legacyMcpUrl}`);
614
646
  }
615
647
  console.log(``);
616
648
  console.log(`Route AI coding tools through the identity proxy for accurate PID attribution:`);
617
- console.log(` Claude Code: ANTHROPIC_BASE_URL=http://localhost:${port}/proxy claude`);
618
- console.log(` OpenCode: LLM_BASE_URL=http://localhost:${port}/proxy opencode`);
619
- console.log(` MiMo Code: OPENAI_BASE_URL=http://localhost:${port}/proxy mimo`);
649
+ console.log(` Claude Code: ANTHROPIC_BASE_URL=${proxyUrl} claude`);
650
+ console.log(` OpenCode: LLM_BASE_URL=${proxyUrl} opencode`);
651
+ console.log(` MiMo Code: OPENAI_BASE_URL=${proxyUrl} mimo`);
620
652
  if (open && openWasSpecified) {
621
- openBrowser(url);
653
+ openBrowser(uiUrl);
622
654
  }
623
655
  return;
624
656
  }
625
657
 
626
- console.error(`agent-inspector background server did not become ready at ${url}.`);
658
+ console.error(`agent-inspector background server did not become ready at ${uiUrl}.`);
627
659
  process.exitCode = 1;
628
660
  return;
629
661
  }
630
662
 
631
- console.log(`Server running at ${url}`);
663
+ console.log(`Server running at ${uiUrl}`);
632
664
  console.log(` Capture mode: ${captureMode}`);
633
- console.log(` Proxy: ${url}/proxy`);
634
- printAccessHints(port, host);
635
- if (legacyAliasPort !== null) {
636
- console.log(` Legacy UI: http://localhost:${legacyAliasPort}`);
637
- console.log(` Legacy proxy: http://localhost:${legacyAliasPort}/proxy`);
638
- console.log(` Legacy MCP: http://localhost:${legacyAliasPort}/api/mcp`);
665
+ console.log(` Proxy: ${proxyUrl}`);
666
+ printAccessHints(port, host, basePath);
667
+ if (legacyUrl !== null && legacyProxyUrl !== null && legacyMcpUrl !== null) {
668
+ console.log(` Legacy UI: ${legacyUrl}`);
669
+ console.log(` Legacy proxy: ${legacyProxyUrl}`);
670
+ console.log(` Legacy MCP: ${legacyMcpUrl}`);
639
671
  }
640
672
  console.log(``);
641
673
  console.log(`Route AI coding tools through the proxy:`);
642
- console.log(` Claude Code: ANTHROPIC_BASE_URL=${url}/proxy claude`);
643
- console.log(` OpenCode: LLM_BASE_URL=${url}/proxy opencode`);
644
- console.log(` MiMo Code: OPENAI_BASE_URL=${url}/proxy mimo`);
645
- console.log(
646
- ` Direct HTTP: curl ${url}/proxy/v1/messages -d '{"model":"...","messages":[...]}'`,
647
- );
674
+ console.log(` Claude Code: ANTHROPIC_BASE_URL=${proxyUrl} claude`);
675
+ console.log(` OpenCode: LLM_BASE_URL=${proxyUrl} opencode`);
676
+ console.log(` MiMo Code: OPENAI_BASE_URL=${proxyUrl} mimo`);
677
+ console.log(` Direct HTTP: curl ${proxyMessagesUrl} -d '{"model":"...","messages":[...]}'`);
648
678
  console.log(
649
679
  ` Remote/container tools: use the Network proxy URL above when shown; otherwise restart with --host 0.0.0.0 or a reachable IP.`,
650
680
  );
@@ -657,7 +687,7 @@ async function runStart(args: string[]): Promise<void> {
657
687
  );
658
688
 
659
689
  if (open) {
660
- openBrowser(url);
690
+ openBrowser(uiUrl);
661
691
  }
662
692
 
663
693
  // Compute server path
@@ -691,6 +721,9 @@ async function runStart(args: string[]): Promise<void> {
691
721
  }
692
722
  serverEnv["HOST"] = upstreamHost;
693
723
  serverEnv["NITRO_HOST"] = upstreamHost;
724
+ if (enableIdentityProxy && basePath.length > 0) {
725
+ serverEnv[AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH_ENV] = "1";
726
+ }
694
727
  serverEnv["AGENT_INSPECTOR_PUBLIC_PORT"] = String(port);
695
728
  serverEnv["PROXY_PORT"] = String(port);
696
729
  serverEnv["AGENT_INSPECTOR_CAPTURE_MODE"] = captureMode;
@@ -784,31 +817,32 @@ async function runStart(args: string[]): Promise<void> {
784
817
  serverProcess.unref();
785
818
  // Probe the upstream TanStack port directly — the identity proxy is
786
819
  // not started yet so the public port is not yet accepting traffic.
787
- if (await waitForInspectorHealthy(upstreamPort, 5000, host)) {
788
- console.log(`agent-inspector background server is ready at ${url}`);
820
+ const upstreamProbeBasePath = enableIdentityProxy ? "" : basePath;
821
+ if (await waitForInspectorHealthy(upstreamPort, 5000, host, upstreamProbeBasePath)) {
822
+ console.log(`agent-inspector background server is ready at ${uiUrl}`);
789
823
  if (identityProxies.length > 0) {
790
824
  console.log(`Identity proxy ready on port ${port} (PID-attributing proxy).`);
791
825
  }
792
- printAccessHints(port, host);
793
- if (legacyAliasPort !== null) {
794
- console.log(` Legacy proxy: http://localhost:${legacyAliasPort}/proxy`);
795
- console.log(` Legacy MCP: http://localhost:${legacyAliasPort}/api/mcp`);
826
+ printAccessHints(port, host, basePath);
827
+ if (legacyProxyUrl !== null && legacyMcpUrl !== null) {
828
+ console.log(` Legacy proxy: ${legacyProxyUrl}`);
829
+ console.log(` Legacy MCP: ${legacyMcpUrl}`);
796
830
  }
797
831
  if (identityProxies.length > 0) {
798
832
  console.log(
799
833
  ``,
800
834
  `Route AI coding tools through the identity proxy for accurate PID attribution:`,
801
- ` Claude Code: ANTHROPIC_BASE_URL=http://localhost:${port}/proxy claude`,
802
- ` OpenCode: LLM_BASE_URL=http://localhost:${port}/proxy opencode`,
803
- ` MiMo Code: OPENAI_BASE_URL=http://localhost:${port}/proxy mimo`,
804
- legacyAliasPort === null
835
+ ` Claude Code: ANTHROPIC_BASE_URL=${proxyUrl} claude`,
836
+ ` OpenCode: LLM_BASE_URL=${proxyUrl} opencode`,
837
+ ` MiMo Code: OPENAI_BASE_URL=${proxyUrl} mimo`,
838
+ legacyProxyUrl === null
805
839
  ? ` Override the port with --port <n> when needed.`
806
- : ` Legacy configs using http://localhost:${legacyAliasPort}/proxy still work.`,
840
+ : ` Legacy configs using ${legacyProxyUrl} still work.`,
807
841
  );
808
842
  }
809
843
  return;
810
844
  }
811
- console.error(`agent-inspector background server did not become ready at ${url}.`);
845
+ console.error(`agent-inspector background server did not become ready at ${uiUrl}.`);
812
846
  process.exitCode = 1;
813
847
  return;
814
848
  }
@@ -42,6 +42,7 @@ import {
42
42
 
43
43
  import type { CapturedLog } from "../contracts";
44
44
  import { exportLogsAsZip, type ExportMode } from "../lib/export-logs";
45
+ import { withBasePath } from "../lib/basePath";
45
46
  import type { ImportLogsResponse } from "../lib/logImportContract";
46
47
  import { copyTextToClipboard } from "../lib/clipboard";
47
48
  import type { InspectorGroupEvidence, InspectorGroupMember } from "../lib/groupContract";
@@ -1340,6 +1341,10 @@ export function ProxyViewer({
1340
1341
  const exportAbortRef = useRef<AbortController | null>(null);
1341
1342
  const exportRequestIdRef = useRef(0);
1342
1343
  useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
1344
+ const proxyEndpoint = useMemo(() => {
1345
+ const origin = typeof window === "undefined" ? "http://localhost:9527" : window.location.origin;
1346
+ return `${origin}${withBasePath("/proxy")}`;
1347
+ }, []);
1343
1348
 
1344
1349
  const cancelTransientTasks = useCallback(() => {
1345
1350
  exportRequestIdRef.current += 1;
@@ -2026,8 +2031,12 @@ export function ProxyViewer({
2026
2031
  {liveEmptyStateCopy.showConnectionCommands && (
2027
2032
  <>
2028
2033
  <div className="flex flex-col items-center gap-2">
2029
- <CopyableCommand command="ANTHROPIC_BASE_URL=http://localhost:9527/proxy <your-tool>" />
2030
- <CopyableCommand command="OPENAI_BASE_URL=http://localhost:9527/proxy <your-tool>" />
2034
+ <CopyableCommand
2035
+ command={`ANTHROPIC_BASE_URL=${proxyEndpoint} <your-tool>`}
2036
+ />
2037
+ <CopyableCommand
2038
+ command={`OPENAI_BASE_URL=${proxyEndpoint} <your-tool>`}
2039
+ />
2031
2040
  </div>
2032
2041
  <p className="mx-auto max-w-xl text-xs leading-relaxed text-muted-foreground">
2033
2042
  Container note: if your AI tool runs in a different container or host than
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef, useMemo, useTransition, type
2
2
  import { z } from "zod";
3
3
  import { CapturedLogSchema, type CapturedLog } from "../contracts";
4
4
  import { fetchJson, fetchJsonWithTimeout } from "../lib/apiClient";
5
+ import { withBasePath } from "../lib/basePath";
5
6
  import {
6
7
  DeleteInspectorGroupsResponseSchema,
7
8
  InspectorGroupsListResponseSchema,
@@ -139,7 +140,7 @@ function buildLogsStreamUrl(sessionId: string | undefined): string {
139
140
  params.set("sessionId", sessionId);
140
141
  }
141
142
  const query = params.toString();
142
- return query.length > 0 ? `/api/logs/stream?${query}` : "/api/logs/stream";
143
+ return withBasePath(query.length > 0 ? `/api/logs/stream?${query}` : "/api/logs/stream");
143
144
  }
144
145
 
145
146
  function buildSessionLogsPageUrl(
@@ -13,6 +13,7 @@ import {
13
13
  type PiAgentResponseView,
14
14
  } from "./piAgentChatLogic";
15
15
  import { useProviders } from "../../lib/useProviders";
16
+ import { withBasePath } from "../../lib/basePath";
16
17
  import { dispatchLogFocusRequest } from "../proxy-viewer/logFocus";
17
18
  import { AnswerMarkdown } from "../proxy-viewer/AnswerMarkdown";
18
19
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
@@ -273,7 +274,7 @@ export function PiAgentPanel({
273
274
  logLimit: 16,
274
275
  };
275
276
 
276
- void fetch("/api/pi-agent", {
277
+ void fetch(withBasePath("/api/pi-agent"), {
277
278
  method: "POST",
278
279
  headers: {
279
280
  "content-type": "application/json",