mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-30 01:04:59 +00:00

feat: Initialize LocalAnalyticsCollector in main.py feat: Display session data in local analytics dashboard fix: Use cumulative data from last interaction for dashboard stats fix: Extract initial query from diffs in local analytics collector.
184 lines
7.5 KiB
Python
184 lines
7.5 KiB
Python
import subprocess
|
|
import time
|
|
import os
|
|
import sys
|
|
import threading
|
|
import queue
|
|
|
|
# Define the aider command
|
|
# Use the full path to the .env file
|
|
# Assumes the script is run from the project root or a location where
|
|
# ~/Dev/aider/.env is the correct path.
|
|
# Using sys.executable ensures the script runs aider with the same python env.
|
|
aider_command = [
|
|
sys.executable,
|
|
"-m", "aider",
|
|
"--env-file", os.path.expanduser("~/Dev/aider/.env")
|
|
]
|
|
|
|
# Inputs to send to aider
|
|
inputs = [
|
|
"/ask",
|
|
"what is the reflection error",
|
|
"/exit"
|
|
]
|
|
|
|
# Expected prompts (as bytes, since we read bytes)
|
|
# Use strip() because rich might add spaces or other control characters
|
|
MAIN_PROMPT = b"> "
|
|
EDIT_PROMPT = b"Apply edits? (y/n/commit/diff/quit) "
|
|
|
|
def enqueue_output(out, queue):
|
|
"""Helper function to read output from a stream and put it in a queue."""
|
|
# Read line by line
|
|
for line in iter(out.readline, b''):
|
|
queue.put(line)
|
|
out.close()
|
|
|
|
def run_aider_session():
|
|
print(f"[SCRIPT] Starting aider with command: {' '.join(aider_command)}")
|
|
|
|
# Start the subprocess
|
|
# Use bufsize=1 for line buffering
|
|
# universal_newlines=False to read bytes and reliably detect byte prompts
|
|
# stderr is also piped as rich often prints to stderr
|
|
process = subprocess.Popen(
|
|
aider_command,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
bufsize=1,
|
|
universal_newlines=False
|
|
)
|
|
|
|
# Queues for stdout and stderr
|
|
q_stdout = queue.Queue()
|
|
q_stderr = queue.Queue()
|
|
|
|
# Start threads to read stdout and stderr asynchronously
|
|
t_stdout = threading.Thread(target=enqueue_output, args=(process.stdout, q_stdout))
|
|
t_stderr = threading.Thread(target=enqueue_output, args=(process.stderr, q_stderr))
|
|
t_stdout.daemon = True # Thread dies with the main program
|
|
t_stderr.daemon = True
|
|
t_stdout.start()
|
|
t_stderr.start()
|
|
|
|
# Give aider a moment to start and print initial messages
|
|
time.sleep(3) # Increased initial sleep slightly
|
|
|
|
current_input_index = 0
|
|
# State machine: WAITING_FOR_MAIN_PROMPT, WAITING_FOR_RESPONSE
|
|
state = "WAITING_FOR_MAIN_PROMPT"
|
|
|
|
print(f"[SCRIPT] Initial state: {state}")
|
|
|
|
try:
|
|
# Continue as long as the process is running OR there is output in the queues
|
|
while process.poll() is None or not q_stdout.empty() or not q_stderr.empty():
|
|
try:
|
|
# Get a line from stdout queue with a timeout
|
|
# A small timeout allows the loop to check process.poll() and stderr queue
|
|
line = q_stdout.get(timeout=0.05)
|
|
sys.stdout.buffer.write(line)
|
|
sys.stdout.buffer.flush()
|
|
|
|
# Check for prompts based on the state
|
|
if state == "WAITING_FOR_MAIN_PROMPT":
|
|
# Check if the line ends with the main prompt bytes (after stripping)
|
|
if line.strip().endswith(MAIN_PROMPT.strip()):
|
|
print("\n[SCRIPT] Detected main prompt.")
|
|
if current_input_index < len(inputs):
|
|
command = inputs[current_input_index]
|
|
print(f"[SCRIPT] Sending: {command}")
|
|
process.stdin.write((command + "\n").encode()) # Encode string to bytes
|
|
process.stdin.flush()
|
|
current_input_index += 1
|
|
state = "WAITING_FOR_RESPONSE" # After sending input, wait for response/next prompt
|
|
print(f"[SCRIPT] State transition: {state}")
|
|
else:
|
|
# Should not happen if /exit is the last input, but as a safeguard
|
|
print("[SCRIPT] No more inputs defined, waiting for process exit.")
|
|
state = "SESSION_COMPLETE"
|
|
print(f"[SCRIPT] State transition: {state}")
|
|
|
|
elif state == "WAITING_FOR_RESPONSE":
|
|
# While waiting for response, we might see an edit prompt or the main prompt
|
|
if line.strip().endswith(EDIT_PROMPT.strip()):
|
|
print("\n[SCRIPT] Detected edit prompt.")
|
|
print("[SCRIPT] Sending: n")
|
|
process.stdin.write(b"n\n") # Send 'n' to decline edits
|
|
process.stdin.flush()
|
|
# Stay in WAITING_FOR_RESPONSE state, as declining might lead to another prompt
|
|
print(f"[SCRIPT] State remains: {state}")
|
|
elif line.strip().endswith(MAIN_PROMPT.strip()):
|
|
print("\n[SCRIPT] Detected main prompt (while waiting for response).")
|
|
# Response finished, now ready for next main input
|
|
state = "WAITING_FOR_MAIN_PROMPT"
|
|
print(f"[SCRIPT] State transition: {state}")
|
|
|
|
except queue.Empty:
|
|
# No output from stdout, check stderr queue
|
|
try:
|
|
err_line = q_stderr.get(timeout=0.01)
|
|
sys.stderr.buffer.write(err_line)
|
|
sys.stderr.buffer.flush()
|
|
except queue.Empty:
|
|
# No output from either queue, check if process is still running
|
|
if process.poll() is not None:
|
|
# Process exited and queues are empty, we are done
|
|
print("[SCRIPT] Process exited and queues are empty.")
|
|
break
|
|
# If process is still running but no output, just continue loop and wait
|
|
|
|
# Add a small sleep to prevent tight loop if process is slow to produce output
|
|
time.sleep(0.01)
|
|
|
|
# End of while loop: process exited and queues are drained
|
|
|
|
except Exception as e:
|
|
print(f"[SCRIPT] An error occurred: {e}")
|
|
# Attempt to read remaining output before terminating
|
|
try:
|
|
# Give threads a moment to finish putting data in queues
|
|
t_stdout.join(timeout=1)
|
|
t_stderr.join(timeout=1)
|
|
# Drain queues
|
|
while not q_stdout.empty():
|
|
sys.stdout.buffer.write(q_stdout.get_nowait())
|
|
sys.stdout.buffer.flush()
|
|
while not q_stderr.empty():
|
|
sys.stderr.buffer.write(q_stderr.get_nowait())
|
|
sys.stdout.buffer.flush()
|
|
except Exception as e_drain:
|
|
print(f"[SCRIPT] Error draining queues: {e_drain}")
|
|
|
|
|
|
if process.poll() is None:
|
|
print("[SCRIPT] Terminating process...")
|
|
process.terminate() # Ensure process is terminated on error
|
|
try:
|
|
process.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
print("[SCRIPT] Process did not terminate, killing...")
|
|
process.kill()
|
|
process.wait()
|
|
|
|
|
|
finally:
|
|
# Ensure process is waited upon if not already
|
|
if process.poll() is None:
|
|
print("[SCRIPT] Waiting for process to finish...")
|
|
process.wait()
|
|
|
|
# Final drain of queues just in case
|
|
while not q_stdout.empty():
|
|
sys.stdout.buffer.write(q_stdout.get_nowait())
|
|
sys.stdout.buffer.flush()
|
|
while not q_stderr.empty():
|
|
sys.stderr.buffer.write(q_stderr.get_nowait())
|
|
sys.stderr.buffer.flush()
|
|
|
|
print(f"[SCRIPT] Aider process finished with return code {process.returncode}")
|
|
|
|
if __name__ == "__main__":
|
|
run_aider_session()
|