@tonyclaw/agent-inspector 3.0.44 → 3.0.45

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 (80) hide show
  1. package/.output/cli.js +20743 -16945
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DQHjxFO6.js → CompareDrawer-B3_TLTJh.js} +1 -1
  4. package/.output/public/assets/{InspectorPet-D1A4505B.js → InspectorPet-Cpn6hv4P.js} +1 -1
  5. package/.output/public/assets/{ProxyViewerContainer-BhpIgfBi.js → ProxyViewerContainer-BEAhuGOJ.js} +27 -27
  6. package/.output/public/assets/{ReplayDialog-C5vmbecv.js → ReplayDialog-B3EKVdJk.js} +1 -1
  7. package/.output/public/assets/{RequestAnatomy-CPGNB8lk.js → RequestAnatomy-COOQjyUC.js} +1 -1
  8. package/.output/public/assets/{ResponseView-DUfrajCm.js → ResponseView-DBCjkwye.js} +1 -1
  9. package/.output/public/assets/{StreamingChunkSequence-nbeCQqOz.js → StreamingChunkSequence-CuXhDgMo.js} +1 -1
  10. package/.output/public/assets/{_sessionId-mw3aA8Bp.js → _sessionId-BJlM3nmt.js} +1 -1
  11. package/.output/public/assets/{_sessionId-CDhRgzf6.js → _sessionId-zbgYl7ZJ.js} +1 -1
  12. package/.output/public/assets/{index-B2eoeT-Z.js → index-BIK4bPTv.js} +1 -1
  13. package/.output/public/assets/{index-C6qZwYlR.js → index-BZo_8koU.js} +15 -15
  14. package/.output/public/assets/{index-CX7_rf7o.js → index-D2Qw6Ri2.js} +1 -1
  15. package/.output/public/assets/{index-BWjeqxbk.css → index-ZWschkCh.css} +1 -1
  16. package/.output/public/assets/{index-BfRhTvOd.js → index-xF_NY8Yb.js} +5 -5
  17. package/.output/public/assets/{json-viewer-BLmF1sOZ.js → json-viewer-B-Ao6pwW.js} +1 -1
  18. package/.output/public/assets/{jszip.min-C9rCFoU3.js → jszip.min-B_rfAUCe.js} +1 -1
  19. package/.output/server/_libs/modelcontextprotocol__core.mjs +25 -25
  20. package/.output/server/_libs/modelcontextprotocol__server.mjs +2 -2
  21. package/.output/server/_libs/zod.mjs +6 -6
  22. package/.output/server/{_sessionId-U54BAqdp.mjs → _sessionId-CJ99gZFV.mjs} +3 -1
  23. package/.output/server/{_sessionId-E_Dmopc-.mjs → _sessionId-DEjScBpK.mjs} +5 -3
  24. package/.output/server/_ssr/{CompareDrawer-CksatPed.mjs → CompareDrawer-DtOIMeQB.mjs} +4 -2
  25. package/.output/server/_ssr/{InspectorPet-DNc4GQ7z.mjs → InspectorPet-CXc4oCBQ.mjs} +3 -1
  26. package/.output/server/_ssr/{ProxyViewerContainer-B02Ey0p_.mjs → ProxyViewerContainer-DVPlwfkt.mjs} +52 -27
  27. package/.output/server/_ssr/{ReplayDialog-DdMvHJF1.mjs → ReplayDialog-BQEA95qC.mjs} +6 -4
  28. package/.output/server/_ssr/{RequestAnatomy--vh2Z8__.mjs → RequestAnatomy-BMyd8B7Q.mjs} +4 -2
  29. package/.output/server/_ssr/{ResponseView-Dhxc-cKC.mjs → ResponseView-qrTaEIM8.mjs} +4 -2
  30. package/.output/server/_ssr/{StreamingChunkSequence-BG0EQmaD.mjs → StreamingChunkSequence-BFTL8mIy.mjs} +5 -3
  31. package/.output/server/_ssr/{index-lcfulIXa.mjs → index-Bcj1Hcy4.mjs} +4 -2
  32. package/.output/server/_ssr/{index-D0At9nfd.mjs → index-DW-M7XUG.mjs} +3 -1
  33. package/.output/server/_ssr/index.mjs +4 -3
  34. package/.output/server/_ssr/{json-viewer-My1J1_5R.mjs → json-viewer-DiI0Yjsn.mjs} +4 -2
  35. package/.output/server/_ssr/publicBasePathContext-5264L3rX.mjs +148 -0
  36. package/.output/server/_ssr/{router-DWcUFPR3.mjs → router-BJ_ltLim.mjs} +2432 -475
  37. package/.output/server/_ssr/start-C6MjPjxp.mjs +57 -0
  38. package/.output/server/_tanstack-start-manifest_v-C6pXXX9R.mjs +4 -0
  39. package/.output/server/index.mjs +92 -92
  40. package/.output/workers/logFinalizer.worker.js +4 -0
  41. package/.output/workers/sessionWorkerEntry.js +4 -0
  42. package/README.md +221 -25
  43. package/docs/instance-control.openapi.yaml +306 -0
  44. package/package.json +4 -1
  45. package/src/cli/alias.ts +440 -0
  46. package/src/cli/instance.ts +1037 -0
  47. package/src/cli/instanceArgs.ts +266 -0
  48. package/src/cli/instanceControl.ts +183 -0
  49. package/src/cli/instanceModel.ts +275 -0
  50. package/src/cli/instanceRegistry.ts +740 -0
  51. package/src/cli/onboard.ts +123 -35
  52. package/src/cli/onboardTarget.ts +179 -0
  53. package/src/cli/templates/codex-skill-onboard.ts +76 -28
  54. package/src/cli/templates/skill-onboard.ts +93 -59
  55. package/src/cli.ts +154 -16
  56. package/src/components/ProxyViewer.tsx +13 -5
  57. package/src/components/providers/SettingsDialog.tsx +13 -8
  58. package/src/lib/basePath.ts +8 -2
  59. package/src/lib/codingAgentEndpoints.ts +16 -0
  60. package/src/lib/instanceContract.ts +220 -0
  61. package/src/lib/managedInstance.ts +104 -0
  62. package/src/lib/publicBasePathContext.ts +107 -0
  63. package/src/mcp/instanceHandlers.ts +149 -0
  64. package/src/mcp/loopback.ts +16 -5
  65. package/src/mcp/mode.ts +25 -0
  66. package/src/mcp/server.ts +258 -18
  67. package/src/mcp/toolHandlers.ts +2 -2
  68. package/src/proxy/identityProxy.ts +655 -36
  69. package/src/proxy/runtimeShutdown.ts +73 -1
  70. package/src/proxy/sessionInfo.ts +5 -4
  71. package/src/routes/api/-instances.ts +109 -0
  72. package/src/routes/api/instances.$name.connection.ts +12 -0
  73. package/src/routes/api/instances.$name.restart.ts +23 -0
  74. package/src/routes/api/instances.$name.start.ts +18 -0
  75. package/src/routes/api/instances.$name.stop.ts +23 -0
  76. package/src/routes/api/instances.$name.ts +12 -0
  77. package/src/routes/api/instances.ts +16 -0
  78. package/src/start.ts +10 -0
  79. package/.output/server/_ssr/start-HYkvq4Ni.mjs +0 -4
  80. package/.output/server/_tanstack-start-manifest_v-B2chR8ef.mjs +0 -4
package/src/cli.ts CHANGED
@@ -6,11 +6,18 @@ 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 packageJson from "../package.json";
9
10
  import {
10
11
  AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH_ENV,
11
12
  appendBasePathToOrigin,
12
13
  getConfiguredBasePath,
13
14
  } from "./lib/basePath.js";
15
+ import {
16
+ AGENT_INSPECTOR_CLI_ENTRY_ENV,
17
+ managedInstanceShutdownMessage,
18
+ readManagedInstanceLaunch,
19
+ type ManagedInstanceLaunch,
20
+ } from "./lib/managedInstance.js";
14
21
  import {
15
22
  formatAccessHintLines,
16
23
  isLoopbackHost,
@@ -29,6 +36,7 @@ import {
29
36
  forceCloseIdentityProxy,
30
37
  startIdentityProxy,
31
38
  stopIdentityProxyAdmission,
39
+ type ManagedInstanceControlOptions,
32
40
  } from "./proxy/identityProxy.js";
33
41
  import { findPidsByPort, killPid, openUrlCommand } from "./proxy/platformCommands.js";
34
42
 
@@ -61,19 +69,71 @@ process.title = "Agent Inspector";
61
69
  */
62
70
  const subcommand = process.argv[2];
63
71
 
64
- if (subcommand === "onboard") {
65
- const { runOnboard } = await import("./cli/onboard.js");
66
- const code = await runOnboard(process.argv.slice(3));
67
- process.exit(code);
68
- }
72
+ function printGlobalHelp(): void {
73
+ console.log(`agent-inspector ${packageJson.version} - Agent observability and knowledge capture
74
+
75
+ Usage:
76
+ agent-inspector [start] [options]
77
+ agent-inspector instance <start|list|status|connect|stop|restart> ...
78
+ agent-inspector alias <list|add|remove> ...
79
+ agent-inspector onboard [options]
80
+ agent-inspector doctor [options]
81
+
82
+ Global options:
83
+ -h, --help Show this help without starting a runtime
84
+ -v, --version Print the package version
69
85
 
70
- if (subcommand === "doctor") {
71
- const { runDoctor } = await import("./cli/doctor.js");
72
- const code = await runDoctor(process.argv.slice(3));
73
- process.exit(code);
86
+ Run \`agent-inspector instance --help\`, \`agent-inspector onboard --help\`, or
87
+ \`agent-inspector doctor --help\` for command-specific options.`);
74
88
  }
75
89
 
76
- await runStart(process.argv.slice(2));
90
+ switch (subcommand) {
91
+ case "--help":
92
+ case "-h":
93
+ printGlobalHelp();
94
+ process.exitCode = 0;
95
+ break;
96
+ case "--version":
97
+ case "-v":
98
+ console.log(packageJson.version);
99
+ process.exitCode = 0;
100
+ break;
101
+ case "onboard": {
102
+ const { runOnboard } = await import("./cli/onboard.js");
103
+ const code = await runOnboard(process.argv.slice(3));
104
+ process.exit(code);
105
+ }
106
+ case "doctor": {
107
+ const { runDoctor } = await import("./cli/doctor.js");
108
+ const code = await runDoctor(process.argv.slice(3));
109
+ process.exit(code);
110
+ }
111
+ case "alias": {
112
+ const { runAlias } = await import("./cli/alias.js");
113
+ const code = await runAlias(process.argv.slice(3));
114
+ process.exit(code);
115
+ }
116
+ case "instance": {
117
+ const { runInstance } = await import("./cli/instance.js");
118
+ const code = await runInstance(process.argv.slice(3));
119
+ process.exitCode = code;
120
+ break;
121
+ }
122
+ case undefined:
123
+ await runStart(process.argv.slice(2));
124
+ break;
125
+ case "start":
126
+ await runStart(process.argv.slice(3));
127
+ break;
128
+ default:
129
+ if (subcommand.startsWith("-")) {
130
+ await runStart(process.argv.slice(2));
131
+ } else {
132
+ console.error(`agent-inspector: unknown command ${subcommand}`);
133
+ console.error("Run `agent-inspector --help` for supported commands.");
134
+ process.exitCode = 2;
135
+ }
136
+ }
77
137
 
78
138
  // -----------------------------------------------------------------------------
79
139
  // Legacy `start` behavior — start the proxy on the configured port. Extracted
@@ -345,6 +405,7 @@ async function tryStartIdentityProxy(
345
405
  upstreamPort: number,
346
406
  listenHost: string,
347
407
  upstreamHost: string,
408
+ managedInstance: ManagedInstanceControlOptions | undefined,
348
409
  ): Promise<import("node:http").Server | null> {
349
410
  try {
350
411
  const server = await startIdentityProxy({
@@ -364,6 +425,7 @@ async function tryStartIdentityProxy(
364
425
  .filter((host) => host.length > 0),
365
426
  maxRequestBytes: readPositiveIntegerEnv("AGENT_INSPECTOR_MAX_REQUEST_BYTES"),
366
427
  requestTimeoutMs: readPositiveIntegerEnv("AGENT_INSPECTOR_REQUEST_TIMEOUT_MS"),
428
+ managedInstance,
367
429
  });
368
430
  return server;
369
431
  } catch (err) {
@@ -378,6 +440,14 @@ async function tryStartIdentityProxy(
378
440
  }
379
441
 
380
442
  async function runStart(args: string[]): Promise<void> {
443
+ const managedLaunchResult = readManagedInstanceLaunch(process.env);
444
+ if (managedLaunchResult.kind === "invalid") {
445
+ console.error(managedLaunchResult.message);
446
+ process.exitCode = 1;
447
+ return;
448
+ }
449
+ const managedLaunch: ManagedInstanceLaunch | null =
450
+ managedLaunchResult.kind === "managed" ? managedLaunchResult.launch : null;
381
451
  const envPort = process.env["AGENT_INSPECTOR_PORT"] ?? process.env["PORT"];
382
452
  const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
383
453
  const envHost = process.env["NITRO_HOST"] ?? process.env["HOST"];
@@ -417,6 +487,10 @@ async function runStart(args: string[]): Promise<void> {
417
487
 
418
488
  for (let i = 0; i < args.length; i++) {
419
489
  const arg = args[i] ?? "";
490
+ if (arg.startsWith("--port=")) {
491
+ port = Number(arg.slice(arg.indexOf("=") + 1));
492
+ continue;
493
+ }
420
494
  if (arg.startsWith("--host=")) {
421
495
  const value = arg.slice(arg.indexOf("=") + 1).trim();
422
496
  if (!isValidBindHost(value)) {
@@ -515,8 +589,19 @@ async function runStart(args: string[]): Promise<void> {
515
589
  case "--legacy-port":
516
590
  legacyAliasEnabled = true;
517
591
  break;
592
+ case "--help":
593
+ case "-h":
594
+ printGlobalHelp();
595
+ return;
596
+ case "--version":
597
+ case "-v":
598
+ console.log(packageJson.version);
599
+ return;
518
600
  default:
519
- break;
601
+ console.error(`agent-inspector start: unknown option ${arg}`);
602
+ console.error("Run `agent-inspector --help` for supported options.");
603
+ process.exitCode = 2;
604
+ return;
520
605
  }
521
606
  }
522
607
 
@@ -551,7 +636,22 @@ async function runStart(args: string[]): Promise<void> {
551
636
  // TanStack Start listens on the private upstream port; identity proxies own
552
637
  // the public-facing port(s). When the identity proxy is disabled we fall
553
638
  // back to TanStack listening on the public port directly.
554
- const upstreamPort = enableIdentityProxy ? pickUpstreamPort(port, legacyAliasPort) : port;
639
+ const upstreamPort =
640
+ managedLaunch === null
641
+ ? enableIdentityProxy
642
+ ? pickUpstreamPort(port, legacyAliasPort)
643
+ : port
644
+ : managedLaunch.upstreamPort;
645
+ if (
646
+ upstreamPort < 1 ||
647
+ upstreamPort > 65_535 ||
648
+ (enableIdentityProxy && upstreamPort === port) ||
649
+ upstreamPort === legacyAliasPort
650
+ ) {
651
+ console.error(`Invalid managed upstream port: ${String(upstreamPort)}.`);
652
+ process.exitCode = 1;
653
+ return;
654
+ }
555
655
  const upstreamHost = enableIdentityProxy ? DEFAULT_BIND_HOST : host;
556
656
  process.env["PORT"] = String(upstreamPort);
557
657
 
@@ -629,7 +729,7 @@ async function runStart(args: string[]): Promise<void> {
629
729
  const supervisorProcess = spawn(process.execPath, [cliEntry, ...supervisorArgs], {
630
730
  stdio: "ignore",
631
731
  detached: true,
632
- env: process.env,
732
+ env: { ...process.env, [AGENT_INSPECTOR_CLI_ENTRY_ENV]: cliEntry },
633
733
  windowsHide: true,
634
734
  });
635
735
  supervisorProcess.once("error", (err) => {
@@ -697,6 +797,10 @@ async function runStart(args: string[]): Promise<void> {
697
797
 
698
798
  // Start the server with the branded Windows runtime when postinstall created it.
699
799
  const serverEnv = { ...process.env };
800
+ const currentCliEntry = process.env[AGENT_INSPECTOR_CLI_ENTRY_ENV] ?? process.argv[1];
801
+ if (currentCliEntry !== undefined && currentCliEntry.length > 0) {
802
+ serverEnv[AGENT_INSPECTOR_CLI_ENTRY_ENV] = currentCliEntry;
803
+ }
700
804
  if (configDir !== undefined) {
701
805
  // Normalize MSYS / Git Bash paths to Windows native form.
702
806
  // On Windows, `path.join('/c/Users/foo')` becomes `\c\Users\foo`
@@ -732,7 +836,14 @@ async function runStart(args: string[]): Promise<void> {
732
836
  serverEnv["AGENT_INSPECTOR_WORKER_DIR"] = workerDir;
733
837
  }
734
838
  const serverProcess = spawn(serverCommand.command, serverCommand.args, {
735
- stdio: background ? ["ignore", "ignore", "ignore"] : ["ignore", "pipe", "pipe"],
839
+ stdio:
840
+ managedLaunch === null
841
+ ? background
842
+ ? ["ignore", "ignore", "ignore"]
843
+ : ["ignore", "pipe", "pipe"]
844
+ : background
845
+ ? ["ignore", "ignore", "ignore", "ipc"]
846
+ : ["ignore", "pipe", "pipe", "ipc"],
736
847
  detached: background,
737
848
  env: serverEnv,
738
849
  windowsHide: background,
@@ -764,7 +875,13 @@ async function runStart(args: string[]): Promise<void> {
764
875
  const requestSupervisorShutdown = (signal: "SIGINT" | "SIGTERM"): void => {
765
876
  if (supervisorShutdownPromise !== null) return;
766
877
  for (const proxy of identityProxies) stopIdentityProxyAdmission(proxy.server);
767
- serverProcess.kill(signal);
878
+ if (managedLaunch !== null && serverProcess.connected) {
879
+ serverProcess.send(managedInstanceShutdownMessage(managedLaunch.controlToken), (error) => {
880
+ if (error !== null && serverProcess.exitCode === null) serverProcess.kill(signal);
881
+ });
882
+ } else {
883
+ serverProcess.kill(signal);
884
+ }
768
885
 
769
886
  const childDeadline =
770
887
  readPositiveIntegerEnv("AGENT_INSPECTOR_SHUTDOWN_TIMEOUT_MS") ?? DEFAULT_SHUTDOWN_DEADLINE_MS;
@@ -795,8 +912,28 @@ async function runStart(args: string[]): Promise<void> {
795
912
  const currentSupervisorShutdown = (): Promise<void> | null => supervisorShutdownPromise;
796
913
  process.on("SIGINT", handleSigint);
797
914
  process.on("SIGTERM", handleSigterm);
915
+ const managedInstanceControl: ManagedInstanceControlOptions | undefined =
916
+ managedLaunch === null
917
+ ? undefined
918
+ : {
919
+ name: managedLaunch.name,
920
+ instanceId: managedLaunch.instanceId,
921
+ launchId: managedLaunch.launchId,
922
+ controlToken: managedLaunch.controlToken,
923
+ supervisorPid: process.pid,
924
+ startedAt: managedLaunch.startedAt,
925
+ publicPort: port,
926
+ upstreamPort,
927
+ requestShutdown: () => requestSupervisorShutdown("SIGTERM"),
928
+ };
798
929
  if (enableIdentityProxy) {
799
- const primaryProxy = await tryStartIdentityProxy(port, upstreamPort, host, upstreamHost);
930
+ const primaryProxy = await tryStartIdentityProxy(
931
+ port,
932
+ upstreamPort,
933
+ host,
934
+ upstreamHost,
935
+ managedInstanceControl,
936
+ );
800
937
  if (primaryProxy !== null) {
801
938
  identityProxies.push({ label: "primary", port, server: primaryProxy });
802
939
  }
@@ -806,6 +943,7 @@ async function runStart(args: string[]): Promise<void> {
806
943
  upstreamPort,
807
944
  host,
808
945
  upstreamHost,
946
+ managedInstanceControl,
809
947
  );
810
948
  if (legacyProxy !== null) {
811
949
  identityProxies.push({ label: "legacy", port: legacyAliasPort, server: legacyProxy });
@@ -43,7 +43,8 @@ import {
43
43
 
44
44
  import type { CapturedLog } from "../contracts";
45
45
  import { exportLogsAsZip, type ExportMode } from "../lib/export-logs";
46
- import { withBasePath } from "../lib/basePath";
46
+ import { getConfiguredBasePath, withBasePath } from "../lib/basePath";
47
+ import { deriveCodingAgentEndpoints } from "../lib/codingAgentEndpoints";
47
48
  import type { ImportLogsResponse } from "../lib/logImportContract";
48
49
  import { copyTextToClipboard } from "../lib/clipboard";
49
50
  import type { InspectorGroupEvidence, InspectorGroupMember } from "../lib/groupContract";
@@ -1384,9 +1385,9 @@ export function ProxyViewer({
1384
1385
  const exportRequestIdRef = useRef(0);
1385
1386
  const detailsPanelResizeRef = useRef<DetailsPanelResizeState | null>(null);
1386
1387
  useKeyboardNavigation(logListRef, logListWrapperRef, { pageWide: true });
1387
- const proxyEndpoint = useMemo(() => {
1388
+ const connectionEndpoints = useMemo(() => {
1388
1389
  const origin = typeof window === "undefined" ? "http://localhost:9527" : window.location.origin;
1389
- return `${origin}${withBasePath("/proxy")}`;
1390
+ return deriveCodingAgentEndpoints(origin, getConfiguredBasePath());
1390
1391
  }, []);
1391
1392
 
1392
1393
  const cancelTransientTasks = useCallback(() => {
@@ -2146,11 +2147,18 @@ export function ProxyViewer({
2146
2147
  <>
2147
2148
  <div className="flex flex-col items-center gap-2">
2148
2149
  <CopyableCommand
2149
- command={`ANTHROPIC_BASE_URL=${proxyEndpoint} <your-tool>`}
2150
+ command={`ANTHROPIC_BASE_URL=${connectionEndpoints.proxy} <your-tool>`}
2150
2151
  />
2151
2152
  <CopyableCommand
2152
- command={`OPENAI_BASE_URL=${proxyEndpoint} <your-tool>`}
2153
+ command={`LLM_BASE_URL=${connectionEndpoints.proxy} opencode`}
2153
2154
  />
2155
+ <CopyableCommand
2156
+ command={`OPENAI_BASE_URL=${connectionEndpoints.openAiV1} <your-tool>`}
2157
+ />
2158
+ <CopyableCommand
2159
+ command={`base_url = "${connectionEndpoints.openAiV1}"`}
2160
+ />
2161
+ <CopyableCommand command={`MCP_URL=${connectionEndpoints.mcp}`} />
2154
2162
  </div>
2155
2163
  <p className="mx-auto max-w-xl text-xs leading-relaxed text-muted-foreground">
2156
2164
  Container note: if your AI tool runs in a different container or host than
@@ -10,7 +10,8 @@ import { useProviders } from "../../lib/useProviders";
10
10
  import { useStripConfig } from "../../lib/useStripConfig";
11
11
  import { copyTextToClipboard } from "../../lib/clipboard";
12
12
  import { fetchJsonWithTimeout } from "../../lib/apiClient";
13
- import { withBasePath } from "../../lib/basePath";
13
+ import { getConfiguredBasePath, withBasePath } from "../../lib/basePath";
14
+ import { deriveCodingAgentEndpoints } from "../../lib/codingAgentEndpoints";
14
15
  import {
15
16
  MAX_PROVIDER_TEST_TIMEOUT_SECONDS,
16
17
  MAX_SLOW_RESPONSE_THRESHOLD_SECONDS,
@@ -333,6 +334,10 @@ function OnboardingSettingsTab(): JSX.Element {
333
334
  if (typeof window === "undefined") return "http://localhost:9527";
334
335
  return window.location.origin;
335
336
  }, []);
337
+ const endpoints = useMemo(
338
+ () => deriveCodingAgentEndpoints(origin, getConfiguredBasePath()),
339
+ [origin],
340
+ );
336
341
  const values = useMemo(
337
342
  () => [
338
343
  { id: "status", label: "Check onboarding", value: "agent-inspector onboard --status" },
@@ -367,7 +372,7 @@ function OnboardingSettingsTab(): JSX.Element {
367
372
  {
368
373
  id: "opencode-proxy",
369
374
  label: "OpenCode proxy",
370
- value: `LLM_BASE_URL=${origin}${withBasePath("/proxy")}`,
375
+ value: `LLM_BASE_URL=${endpoints.proxy}`,
371
376
  },
372
377
  {
373
378
  id: "mimo-config",
@@ -383,22 +388,22 @@ function OnboardingSettingsTab(): JSX.Element {
383
388
  {
384
389
  id: "mimo-proxy",
385
390
  label: "MiMo Code proxy",
386
- value: `OPENAI_BASE_URL=${origin}${withBasePath("/proxy")}`,
391
+ value: `OPENAI_BASE_URL=${endpoints.proxy}`,
387
392
  },
388
- { id: "mcp", label: "MCP URL", value: `${origin}${withBasePath("/api/mcp")}` },
389
- { id: "proxy", label: "Proxy URL", value: `${origin}${withBasePath("/proxy")}` },
393
+ { id: "mcp", label: "MCP URL", value: endpoints.mcp },
394
+ { id: "proxy", label: "Proxy URL", value: endpoints.proxy },
390
395
  {
391
396
  id: "anthropic",
392
397
  label: "Anthropic base",
393
- value: `ANTHROPIC_BASE_URL=${origin}${withBasePath("/proxy")}`,
398
+ value: `ANTHROPIC_BASE_URL=${endpoints.proxy}`,
394
399
  },
395
400
  {
396
401
  id: "openai",
397
402
  label: "OpenAI base",
398
- value: `OPENAI_BASE_URL=${origin}${withBasePath("/proxy")}`,
403
+ value: `OPENAI_BASE_URL=${endpoints.openAiV1}`,
399
404
  },
400
405
  ],
401
- [origin],
406
+ [endpoints],
402
407
  );
403
408
 
404
409
  const handleCopy = useCallback((id: string, value: string) => {
@@ -4,6 +4,8 @@ export const AGENT_INSPECTOR_BASE_PATH_ENV = "AGENT_INSPECTOR_BASE_PATH";
4
4
  export const AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH_ENV =
5
5
  "AGENT_INSPECTOR_INTERNAL_STRIPPED_BASE_PATH";
6
6
  export const AGENT_INSPECTOR_BASE_PATH_META_NAME = "agent-inspector-base-path";
7
+ export const AGENT_INSPECTOR_PUBLIC_BASE_PATH_HEADER = "x-agent-inspector-public-base-path";
8
+ export const AGENT_INSPECTOR_PUBLIC_PORT_HEADER = "x-agent-inspector-public-port";
7
9
  export const DEFAULT_AGENT_INSPECTOR_BASE_PATH = "/inspector";
8
10
 
9
11
  type BasePathEnv = Readonly<Record<string, string | undefined>>;
@@ -35,9 +37,13 @@ function readProcessBasePath(): string | undefined {
35
37
  }
36
38
 
37
39
  export function getConfiguredBasePath(env?: BasePathEnv): string {
40
+ if (env !== undefined) {
41
+ return normalizeBasePath(
42
+ env[AGENT_INSPECTOR_BASE_PATH_ENV] ?? DEFAULT_AGENT_INSPECTOR_BASE_PATH,
43
+ );
44
+ }
38
45
  return normalizeBasePath(
39
- env?.[AGENT_INSPECTOR_BASE_PATH_ENV] ??
40
- readProcessBasePath() ??
46
+ readProcessBasePath() ??
41
47
  readDocumentBasePath() ??
42
48
  import.meta.env?.BASE_URL ??
43
49
  DEFAULT_AGENT_INSPECTOR_BASE_PATH,
@@ -0,0 +1,16 @@
1
+ import { appendBasePathToOrigin } from "./basePath";
2
+
3
+ export type CodingAgentEndpoints = {
4
+ proxy: string;
5
+ openAiV1: string;
6
+ mcp: string;
7
+ };
8
+
9
+ /** Derive the public protocol endpoints shown to coding agents from one Inspector origin. */
10
+ export function deriveCodingAgentEndpoints(origin: string, basePath: string): CodingAgentEndpoints {
11
+ return {
12
+ proxy: appendBasePathToOrigin(origin, "/proxy", basePath),
13
+ openAiV1: appendBasePathToOrigin(origin, "/proxy/v1", basePath),
14
+ mcp: appendBasePathToOrigin(origin, "/api/mcp", basePath),
15
+ };
16
+ }
@@ -0,0 +1,220 @@
1
+ import { z } from "zod";
2
+
3
+ export const INSTANCE_API_SCHEMA_VERSION = 1;
4
+
5
+ export const InstanceStateSchema = z.enum(["running", "stopped", "conflict"]);
6
+ export const InstanceStateReasonSchema = z.enum(["identity_mismatch", "port_conflict"]);
7
+
8
+ export const InstanceUrlsSchema = z
9
+ .object({
10
+ ui: z.url(),
11
+ api: z.url(),
12
+ health: z.url(),
13
+ proxy: z.url(),
14
+ openai: z.url(),
15
+ mcp: z.url(),
16
+ })
17
+ .strict();
18
+
19
+ export const InstanceStatusOutputSchema = z
20
+ .object({
21
+ schemaVersion: z.literal(INSTANCE_API_SCHEMA_VERSION),
22
+ name: z.string().min(1),
23
+ instanceId: z.string().min(1),
24
+ state: InstanceStateSchema,
25
+ stateReason: InstanceStateReasonSchema.nullable(),
26
+ checkedAt: z.iso.datetime(),
27
+ createdAt: z.iso.datetime(),
28
+ updatedAt: z.iso.datetime(),
29
+ launch: z
30
+ .object({
31
+ launchId: z.string().min(1),
32
+ startedAt: z.iso.datetime(),
33
+ })
34
+ .strict(),
35
+ host: z.string().min(1),
36
+ publicPort: z.number().int().min(1).max(65_535),
37
+ upstreamPort: z.number().int().min(1).max(65_535),
38
+ basePath: z.string().startsWith("/"),
39
+ captureMode: z.enum(["simple", "full"]),
40
+ dataDir: z.string().min(1),
41
+ supervisorPid: z.number().int().positive().nullable(),
42
+ urls: InstanceUrlsSchema,
43
+ })
44
+ .strict();
45
+
46
+ export const InstanceConnectionOutputSchema = z
47
+ .object({
48
+ schemaVersion: z.literal(INSTANCE_API_SCHEMA_VERSION),
49
+ instance: InstanceStatusOutputSchema,
50
+ baseUrl: z.url(),
51
+ endpoints: InstanceUrlsSchema,
52
+ environment: z
53
+ .object({
54
+ ANTHROPIC_BASE_URL: z.url(),
55
+ LLM_BASE_URL: z.url(),
56
+ OPENAI_BASE_URL: z.url(),
57
+ AGENT_INSPECTOR_MCP_URL: z.url(),
58
+ })
59
+ .strict(),
60
+ mcp: z
61
+ .object({
62
+ serverName: z.string().min(1),
63
+ transport: z.literal("streamable-http"),
64
+ url: z.url(),
65
+ })
66
+ .strict(),
67
+ codex: z
68
+ .object({
69
+ providerName: z.string().min(1),
70
+ baseUrl: z.url(),
71
+ wireApi: z.literal("responses"),
72
+ })
73
+ .strict(),
74
+ })
75
+ .strict();
76
+
77
+ export const InstanceStartRequestSchema = z
78
+ .object({
79
+ port: z.number().int().min(1).max(65_535).optional(),
80
+ basePath: z
81
+ .string()
82
+ .min(1)
83
+ .max(2_048)
84
+ .refine(
85
+ (value) =>
86
+ value.startsWith("/") &&
87
+ !value.includes("?") &&
88
+ !value.includes("#") &&
89
+ !value.includes("\\"),
90
+ "Base Path must be an absolute URL path without query, hash, or backslash",
91
+ )
92
+ .optional(),
93
+ host: z
94
+ .string()
95
+ .min(1)
96
+ .max(253)
97
+ .refine(
98
+ (value) =>
99
+ value.trim() === value &&
100
+ !value.includes("://") &&
101
+ !value.includes("/") &&
102
+ !value.includes("\\") &&
103
+ !/\s/u.test(value),
104
+ "Host must be a hostname or IP address without protocol, path, or whitespace",
105
+ )
106
+ .optional(),
107
+ captureMode: z.enum(["simple", "full"]).optional(),
108
+ dataDir: z.string().trim().min(1).max(32_767).optional(),
109
+ })
110
+ .strict();
111
+
112
+ export const InstanceEmptyRequestSchema = z.object({}).strict();
113
+
114
+ export const InstanceOperationSchema = z.enum(["start", "stop", "restart"]);
115
+ export const InstanceOperationOutcomeSchema = z.enum([
116
+ "created",
117
+ "started",
118
+ "already_running",
119
+ "stopped",
120
+ "already_stopped",
121
+ "restarted",
122
+ ]);
123
+
124
+ export const InstanceOperationOutputSchema = z
125
+ .object({
126
+ schemaVersion: z.literal(INSTANCE_API_SCHEMA_VERSION),
127
+ operation: InstanceOperationSchema,
128
+ outcome: InstanceOperationOutcomeSchema,
129
+ instance: InstanceStatusOutputSchema,
130
+ })
131
+ .strict();
132
+
133
+ export const InstanceListOutputSchema = z
134
+ .object({
135
+ schemaVersion: z.literal(INSTANCE_API_SCHEMA_VERSION),
136
+ instances: z.array(InstanceStatusOutputSchema),
137
+ })
138
+ .strict();
139
+
140
+ export const InstancePublicErrorCodeSchema = z.enum([
141
+ "argument_invalid",
142
+ "config_mismatch",
143
+ "data_dir_conflict",
144
+ "data_dir_invalid",
145
+ "identity_mismatch",
146
+ "instance_not_found",
147
+ "launch_failed",
148
+ "launch_unavailable",
149
+ "lock_not_owned",
150
+ "lock_timeout",
151
+ "name_invalid",
152
+ "port_conflict",
153
+ "port_exhausted",
154
+ "port_invalid",
155
+ "record_invalid",
156
+ "registry_io",
157
+ "restart_failed_degraded",
158
+ "restart_failed_rolled_back",
159
+ "self_lifecycle_unsupported",
160
+ "shutdown_failed",
161
+ ]);
162
+
163
+ export const InstancePublicErrorSchema = z
164
+ .object({
165
+ code: InstancePublicErrorCodeSchema,
166
+ message: z.string().min(1),
167
+ retryable: z.boolean(),
168
+ })
169
+ .strict();
170
+
171
+ export const InstanceErrorOutputSchema = z
172
+ .object({
173
+ schemaVersion: z.literal(INSTANCE_API_SCHEMA_VERSION),
174
+ error: InstancePublicErrorSchema,
175
+ })
176
+ .strict();
177
+
178
+ export type InstanceState = z.infer<typeof InstanceStateSchema>;
179
+ export type InstanceStateReason = z.infer<typeof InstanceStateReasonSchema>;
180
+ export type InstanceStatusOutput = z.infer<typeof InstanceStatusOutputSchema>;
181
+ export type InstanceConnectionOutput = z.infer<typeof InstanceConnectionOutputSchema>;
182
+ export type InstanceStartRequest = z.infer<typeof InstanceStartRequestSchema>;
183
+ export type InstanceOperation = z.infer<typeof InstanceOperationSchema>;
184
+ export type InstanceOperationOutcome = z.infer<typeof InstanceOperationOutcomeSchema>;
185
+ export type InstanceOperationOutput = z.infer<typeof InstanceOperationOutputSchema>;
186
+ export type InstanceListOutput = z.infer<typeof InstanceListOutputSchema>;
187
+ export type InstancePublicErrorCode = z.infer<typeof InstancePublicErrorCodeSchema>;
188
+ export type InstancePublicError = z.infer<typeof InstancePublicErrorSchema>;
189
+ export type InstanceErrorOutput = z.infer<typeof InstanceErrorOutputSchema>;
190
+
191
+ export function isRetryableInstanceError(code: InstancePublicErrorCode): boolean {
192
+ switch (code) {
193
+ case "launch_failed":
194
+ case "launch_unavailable":
195
+ case "lock_timeout":
196
+ case "port_exhausted":
197
+ case "registry_io":
198
+ case "restart_failed_degraded":
199
+ case "restart_failed_rolled_back":
200
+ case "shutdown_failed":
201
+ return true;
202
+ case "argument_invalid":
203
+ case "config_mismatch":
204
+ case "data_dir_conflict":
205
+ case "data_dir_invalid":
206
+ case "identity_mismatch":
207
+ case "instance_not_found":
208
+ case "lock_not_owned":
209
+ case "name_invalid":
210
+ case "port_conflict":
211
+ case "port_invalid":
212
+ case "record_invalid":
213
+ case "self_lifecycle_unsupported":
214
+ return false;
215
+ }
216
+ }
217
+
218
+ export function instanceErrorOutput(error: InstancePublicError): InstanceErrorOutput {
219
+ return { schemaVersion: INSTANCE_API_SCHEMA_VERSION, error };
220
+ }