jens-l commited on
Commit
4725f58
·
1 Parent(s): c86b694

FIX: Modify test_app_py tool to use free port and fix linters

Browse files
Files changed (2) hide show
  1. app.py +30 -6
  2. kiss_agent.py +43 -92
app.py CHANGED
@@ -39,6 +39,19 @@ signal.signal(signal.SIGINT, signal_handler)
39
  atexit.register(cleanup_preview_on_exit)
40
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  def get_preview_url():
43
  """Get the appropriate preview URL with a cache-busting timestamp."""
44
  # Append a timestamp as a query parameter to break browser cache
@@ -277,10 +290,12 @@ def create_iframe_preview():
277
  <div style="color: #d32f2f; padding: 20px; text-align: center;
278
  border: 1px solid #d32f2f; border-radius: 8px;
279
  background: #ffebee;">
280
- <h3>🚧 Preview App Temporarily Unavailable</h3>
281
- <p><strong>Status:</strong> {message}</p>
282
- <p>The preview app is starting up. Please wait a few seconds
283
- and try refreshing.</p>
 
 
284
  <button onclick="location.reload()" style="
285
  background: #1976d2; color: white; border: none;
286
  padding: 8px 16px; border-radius: 4px; cursor: pointer;">
@@ -893,19 +908,28 @@ if __name__ == "__main__":
893
  print(f"❌ Failed to start preview app: {message}")
894
 
895
  # Parse command line arguments for server configuration
896
- server_port = 7860 # default
897
  server_name = "127.0.0.1" # default
898
 
899
  if "--server-port" in sys.argv:
900
  port_idx = sys.argv.index("--server-port")
901
  if port_idx + 1 < len(sys.argv):
902
- server_port = int(sys.argv[port_idx + 1])
903
 
904
  if "--server-name" in sys.argv:
905
  name_idx = sys.argv.index("--server-name")
906
  if name_idx + 1 < len(sys.argv):
907
  server_name = sys.argv[name_idx + 1]
908
 
 
 
 
 
 
 
 
 
 
909
  GradioUI(agent).launch(
910
  share=False, server_port=server_port, server_name=server_name
911
  )
 
39
  atexit.register(cleanup_preview_on_exit)
40
 
41
 
42
+ def find_free_port(start_port=7860, max_ports=100):
43
+ """Find an available TCP port, starting from a given port."""
44
+ for port in range(start_port, start_port + max_ports):
45
+ try:
46
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
47
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
48
+ sock.bind(("127.0.0.1", port))
49
+ return port
50
+ except OSError:
51
+ print(f" Port {port} is in use, trying next...")
52
+ return None
53
+
54
+
55
  def get_preview_url():
56
  """Get the appropriate preview URL with a cache-busting timestamp."""
57
  # Append a timestamp as a query parameter to break browser cache
 
290
  <div style="color: #d32f2f; padding: 20px; text-align: center;
291
  border: 1px solid #d32f2f; border-radius: 8px;
292
  background: #ffebee;">
293
+ <h3 style="color: #d32f2f;">🚧 Preview App Temporarily Unavailable</h3>
294
+ <p style="color: #333333;"><strong>Status:</strong> {message}</p>
295
+ <p style="color: #333333;">
296
+ The preview app is starting up. Please wait a few seconds
297
+ and try refreshing.
298
+ </p>
299
  <button onclick="location.reload()" style="
300
  background: #1976d2; color: white; border: none;
301
  padding: 8px 16px; border-radius: 4px; cursor: pointer;">
 
908
  print(f"❌ Failed to start preview app: {message}")
909
 
910
  # Parse command line arguments for server configuration
911
+ server_port_arg = 7860 # default
912
  server_name = "127.0.0.1" # default
913
 
914
  if "--server-port" in sys.argv:
915
  port_idx = sys.argv.index("--server-port")
916
  if port_idx + 1 < len(sys.argv):
917
+ server_port_arg = int(sys.argv[port_idx + 1])
918
 
919
  if "--server-name" in sys.argv:
920
  name_idx = sys.argv.index("--server-name")
921
  if name_idx + 1 < len(sys.argv):
922
  server_name = sys.argv[name_idx + 1]
923
 
924
+ # Find an available port for the main app, starting with the desired one
925
+ server_port = find_free_port(server_port_arg)
926
+ if server_port is None:
927
+ print(f"❌ Could not find any available ports starting from {server_port_arg}")
928
+ sys.exit(1)
929
+
930
+ if server_port != server_port_arg:
931
+ print(f"⚠️ Port {server_port_arg} was busy. Running on free port: {server_port}")
932
+
933
  GradioUI(agent).launch(
934
  share=False, server_port=server_port, server_name=server_name
935
  )
kiss_agent.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import re
 
3
  import subprocess
4
  from pathlib import Path
5
 
@@ -164,6 +165,20 @@ def install_package(package_name: str) -> str:
164
  pass
165
 
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  @tool
168
  def python_editor(diff_content: str, filename: str = "app.py") -> str:
169
  """
@@ -321,113 +336,49 @@ def file_viewer(filename: str) -> str:
321
  @tool
322
  def test_app_py() -> str:
323
  """
324
- Test if the current app.py runs without syntax errors and starts successfully.
325
- For server applications like Gradio, this will start the server and then stop it.
326
-
327
- Returns:
328
- Test result message
329
  """
330
  try:
331
  app_path = Path("sandbox") / "app.py"
332
-
333
  if not app_path.exists():
334
- return "Error: app.py does not exist"
335
 
336
- # Store original working directory
337
- original_cwd = os.getcwd()
 
 
338
 
339
- # Change to project directory for testing
340
- os.chdir(app_path.parent)
341
 
342
- # Start the subprocess without waiting for completion
343
  process = subprocess.Popen(
344
- ["python", "app.py"],
345
  stdout=subprocess.PIPE,
346
  stderr=subprocess.PIPE,
347
  text=True,
348
  )
349
-
350
- try:
351
- # Wait for a short time to see if the process starts successfully
352
- # If it exits immediately with an error, we'll catch it
353
- stdout, stderr = process.communicate(timeout=5)
354
-
355
- # If we get here, the process exited within 10 seconds
356
- # Check for errors in stderr or stdout, not just return code
357
- error_indicators = [
358
- "error",
359
- "exception",
360
- "traceback",
361
- "attributeerror",
362
- "importerror",
363
- "modulenotfounderror",
364
- ]
365
-
366
- # Combine stdout and stderr for error checking
367
- all_output = (stdout or "") + (stderr or "")
368
- all_output_lower = all_output.lower()
369
-
370
- # Check if any error indicators are present
371
- has_error = any(
372
- indicator in all_output_lower for indicator in error_indicators
373
  )
 
 
 
 
 
 
 
374
 
375
- if process.returncode != 0 or has_error:
376
- error_output = stderr if stderr else stdout
377
-
378
- # Check specifically for ModuleNotFoundError to
379
- # suggest package installation
380
- if (
381
- "modulenotfounderror" in all_output_lower
382
- or "no module named" in all_output_lower
383
- ):
384
- # Try to extract the missing module name
385
- import_pattern = (
386
- r"(?:ModuleNotFoundError:|No module named) '?([^'\s]+)'?"
387
- )
388
- match = re.search(import_pattern, all_output, re.IGNORECASE)
389
- missing_module = match.group(1) if match else "unknown"
390
- return (
391
- f"❌ ModuleNotFoundError: Missing module '{missing_module}'\n"
392
- f"Consider using the install_package tool to install it.\n"
393
- f"Full error:\n{error_output}"
394
- )
395
-
396
- return f"❌ Error running app.py:\n{error_output}"
397
- else:
398
- return "✅ app.py executed successfully"
399
-
400
- except subprocess.TimeoutExpired:
401
- # Process is still running after 3 seconds - likely a server
402
- # This is actually good news for server apps like Gradio
403
- process.terminate()
404
-
405
- # Wait a bit for graceful termination
406
- try:
407
- process.wait(timeout=2)
408
- except subprocess.TimeoutExpired:
409
- # Force kill if it doesn't terminate gracefully
410
- process.kill()
411
- process.wait()
412
-
413
- # Check if there were any immediate errors in stderr
414
- try:
415
- _, stderr = process.communicate(timeout=1)
416
- if stderr and "error" in stderr.lower():
417
- return f"❌ Error detected in app.py:\n{stderr}"
418
- except Exception:
419
- pass
420
-
421
- return "✅ app.py started successfully (server detected and stopped)"
422
-
423
  except Exception as e:
424
- return f"Error testing app.py: {str(e)}"
425
- finally:
426
- # Change back to original directory
427
- try:
428
- os.chdir(original_cwd)
429
- except NameError:
430
- pass
431
 
432
 
433
  class KISSAgent(ToolCallingAgent):
 
1
  import os
2
  import re
3
+ import socket
4
  import subprocess
5
  from pathlib import Path
6
 
 
165
  pass
166
 
167
 
168
+ def _find_free_port(start_port=7860, max_ports=100):
169
+ """Find an available TCP port, starting from a given port."""
170
+ for port in range(start_port, start_port + max_ports):
171
+ try:
172
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
173
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
174
+ sock.bind(("127.0.0.1", port))
175
+ return port
176
+ except OSError:
177
+ # This port is in use, try the next one
178
+ continue
179
+ return None
180
+
181
+
182
  @tool
183
  def python_editor(diff_content: str, filename: str = "app.py") -> str:
184
  """
 
336
  @tool
337
  def test_app_py() -> str:
338
  """
339
+ Test the app.py file by running it as a subprocess and checking its output.
340
+ This tool will automatically find a free port to run the app on.
 
 
 
341
  """
342
  try:
343
  app_path = Path("sandbox") / "app.py"
 
344
  if not app_path.exists():
345
+ return "Error: app.py not found in sandbox directory."
346
 
347
+ # Find a free port to avoid conflicts
348
+ free_port = _find_free_port()
349
+ if free_port is None:
350
+ return "Error: Could not find any free ports to run the test."
351
 
352
+ print(f"Found free port {free_port}, running test...")
 
353
 
354
+ # Run the app as a subprocess with a timeout
355
  process = subprocess.Popen(
356
+ ["python", str(app_path), "--server-port", str(free_port)],
357
  stdout=subprocess.PIPE,
358
  stderr=subprocess.PIPE,
359
  text=True,
360
  )
361
+ stdout, stderr = process.communicate(timeout=15)
362
+
363
+ if "Running on local URL" in stdout:
364
+ return "✅ Test passed: App launched successfully."
365
+ elif process.returncode != 0:
366
+ error_message = (
367
+ f"❌ Test failed: App exited with code {process.returncode}\n"
368
+ f"---STDERR---\n{stderr}\n---STDOUT---\n{stdout}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
  )
370
+ return error_message
371
+ else:
372
+ inconclusive_message = (
373
+ f"⚠️ Test inconclusive: App ran without clear success message.\n"
374
+ f"---STDERR---\n{stderr}\n---STDOUT---\n{stdout}"
375
+ )
376
+ return inconclusive_message
377
 
378
+ except subprocess.TimeoutExpired:
379
+ return "❌ Test failed: App ran for over 15 seconds and was terminated."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  except Exception as e:
381
+ return f" An unexpected error occurred during testing: {str(e)}"
 
 
 
 
 
 
382
 
383
 
384
  class KISSAgent(ToolCallingAgent):