diff --git a/src/pyob/entrance.py b/src/pyob/entrance.py
index ea6f993..cbcc872 100644
--- a/src/pyob/entrance.py
+++ b/src/pyob/entrance.py
@@ -51,6 +51,9 @@ def __init__(self, target_dir: str):
self.analysis_path = os.path.join(self.pyob_dir, "ANALYSIS.md")
self.history_path = os.path.join(self.pyob_dir, "HISTORY.md")
self.symbols_path = os.path.join(self.pyob_dir, "SYMBOLS.json")
+ self.memory_path = os.path.join(
+ self.pyob_dir, "MEMORY.md"
+ ) # For interactive memory editor
self.llm_engine = AutoReviewer(self.target_dir)
self.code_parser = CodeParser()
self.ledger = self.load_ledger()
@@ -347,8 +350,8 @@ def execute_targeted_iteration(self, iteration: int):
)
for f_name in self.ENGINE_FILES:
src = os.path.join(self.target_dir, "src", "pyob", f_name)
- if not os.path.exists(src):
- src = os.path.join(self.target_dir, f_name)
+ # Assuming src/pyob is the canonical location for engine files,
+ # consistent with PYTHONPATH setup in reboot_pyob.
if os.path.exists(src):
shutil.copy(src, str(pod_path))
except Exception as e:
@@ -567,7 +570,7 @@ def detect_symbolic_ripples(
changed_text = "\n".join(
[line for line in diff if line.startswith("+") or line.startswith("-")]
)
- potential_symbols = set(re.findall(r"([a-zA-Z0-9_$]{4,})", changed_text))
+ potential_symbols = set(re.findall(r"\b[a-zA-Z_][a-zA-Z0-9_]*\b", changed_text))
impacted_files = []
for sym in potential_symbols:
if self.ledger["definitions"].get(sym) == source_file:
diff --git a/src/pyob/pyob_dashboard.py b/src/pyob/pyob_dashboard.py
index 95409a3..c04cb21 100644
--- a/src/pyob/pyob_dashboard.py
+++ b/src/pyob/pyob_dashboard.py
@@ -38,9 +38,31 @@
}
.status-pill { padding: 4px 12px; border-radius: 20px; font-size: 0.7rem; font-weight: 800; background: #222; }
.evolving { color: var(--accent); border: 1px solid var(--accent); box-shadow: 0 0 10px #00ffa344; }
- input { background: #000; border: 1px solid #2a2a30; color: var(--accent); padding: 10px; border-radius: 4px; width: 100%; font-family: 'JetBrains Mono'; margin-bottom: 10px; }
+ input, textarea { background: #000; border: 1px solid #2a2a30; color: var(--accent); padding: 10px; border-radius: 4px; width: 100%; font-family: 'JetBrains Mono'; margin-bottom: 10px; }
button { width: 100%; padding: 12px; background: var(--accent); color: #000; border: none; border-radius: 4px; font-weight: 700; cursor: pointer; transition: 0.2s; }
button:hover { filter: brightness(1.2); }
+ /* Specific styles for queue items */
+ .queue-item {
+ margin-bottom: 8px;
+ padding: 8px;
+ background: #00000066;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-family: 'JetBrains Mono';
+ font-size: 0.8em;
+ color: #ced4e0;
+ }
+ .queue-item button {
+ width: auto;
+ padding: 5px 10px;
+ font-size: 0.7em;
+ margin-left: 5px;
+ border-radius: 3px;
+ }
+ .queue-item .move-btn { background: #4a4a50; color: var(--text); }
+ .queue-item .remove-btn { background: #cc0000; color: #fff; }
@@ -56,7 +78,8 @@
Logic Memory (MEMORY.md)
-
Initializing brain...
+
+
System Logs (HISTORY.md)
@@ -66,14 +89,23 @@
Architectural Analysis
Scanning structure...
+
+
Pending Patch Reviews
+
No pending patches.
+
+
+
Manual Cascade Injection
+
+
+
Queue Status
-
IDLE
+
IDLE
@@ -89,12 +121,34 @@
const isEvolving = data.cascade_queue?.length > 0 || data.patches_count > 0;
pill.innerText = isEvolving ? "EVOLVING" : "STABLE";
pill.className = isEvolving ? "status-pill evolving" : "status-pill";
- document.getElementById('memory').innerText = data.memory || "Brain empty.";
+ document.getElementById('memory').value = data.memory || "Brain empty."; // Changed to .value for textarea
document.getElementById('history').innerText = data.history || "No logs.";
document.getElementById('analysis').innerText = data.analysis || "Parsing...";
+
+ // --- START NEW QUEUE RENDERING LOGIC ---
const queueDiv = document.getElementById('queue');
- queueDiv.innerText = data.cascade_queue?.length > 0 ? data.cascade_queue.join('\\n') : "EMPTY";
- } catch (e) { document.getElementById('status-pill').innerText = "OFFLINE"; }
+ queueDiv.innerHTML = ''; // Clear previous content
+ if (data.cascade_queue && data.cascade_queue.length > 0) {
+ data.cascade_queue.forEach((item, index) => {
+ const itemElement = document.createElement('div');
+ itemElement.className = 'queue-item';
+ itemElement.innerHTML = `
+ ${item}
+
+
+
+
+
+ `;
+ queueDiv.appendChild(itemElement);
+ });
+ } else {
+ queueDiv.innerText = "EMPTY";
+ }
+ // --- END NEW QUEUE RENDERING LOGIC ---
+
+ await updatePendingPatches(); // Refresh pending patches
+ } catch (e) { document.getElementById('status-pill').innerText = "OFFLINE"; console.error("Error updating stats:", e); }
}
async function setManualTarget() {
@@ -108,6 +162,130 @@
document.getElementById('manualTargetFile').value = '';
}
+ async function updatePendingPatches() {
+ try {
+ const response = await fetch('/api/pending_patches');
+ const data = await response.json();
+ const patchesDiv = document.getElementById('pending-patches');
+ patchesDiv.innerHTML = ''; // Clear previous content
+
+ if (data.patches && data.patches.length > 0) {
+ data.patches.forEach(patch => {
+ const patchElement = document.createElement('div');
+ patchElement.style.marginBottom = '10px';
+ patchElement.innerHTML = `
+ Patch ID: ${patch.id}
+ File: ${patch.target_file}
+ Description: ${patch.description || 'N/A'}
+
+
+ `;
+ patchesDiv.appendChild(patchElement);
+ });
+ } else {
+ patchesDiv.innerText = "No pending patches.";
+ }
+ } catch (e) {
+ console.error("Failed to fetch pending patches:", e);
+ document.getElementById('pending-patches').innerText = "Error loading patches.";
+ }
+ }
+
+ async function reviewPatch(patchId, action) {
+ try {
+ await fetch('/api/review_patch', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ patch_id: patchId, action: action })
+ });
+ // Refresh stats and patches after review
+ await updateStats();
+ } catch (e) {
+ console.error(`Failed to ${action} patch ${patchId}:`, e);
+ alert(`Failed to ${action} patch ${patchId}. Check console for details.`);
+ }
+ }
+
+ async function saveMemory() {
+ const memoryContent = document.getElementById('memory').value;
+ try {
+ const response = await fetch('/api/update_memory', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: memoryContent })
+ });
+ const result = await response.json();
+ if (response.ok) {
+ alert('Logic Memory saved successfully!');
+ await updateStats(); // Refresh to ensure consistency
+ } else {
+ alert(`Failed to save Logic Memory: ${result.error}`);
+ }
+ } catch (e) {
+ console.error("Failed to save Logic Memory:", e);
+ alert("Error saving Logic Memory. Check console for details.");
+ }
+ }
+
+ async function addCascadeItem() {
+ const item = document.getElementById('cascadeItem').value;
+ if (!item) {
+ alert('Please enter an item to add to the cascade queue.');
+ return;
+ }
+ try {
+ const response = await fetch('/api/cascade_queue/add', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ item: item })
+ });
+ const result = await response.json();
+ if (response.ok) {
+ alert(`'${item}' added to cascade queue.`);
+ document.getElementById('cascadeItem').value = ''; // Clear input
+ await updateStats(); // Refresh queue display
+ } else {
+ alert(`Failed to add item to queue: ${result.error}`);
+ }
+ } catch (e) {
+ console.error("Failed to add item to cascade queue:", e);
+ alert("Error adding item to cascade queue. Check console for details.");
+ }
+ }
+
+ // --- START NEW QUEUE INTERACTION FUNCTIONS ---
+ async function moveQueueItem(itemId, direction) {
+ try {
+ await fetch('/api/cascade_queue/move', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ item_id: itemId, direction: direction })
+ });
+ await updateStats(); // Refresh queue after move
+ } catch (e) {
+ console.error(`Failed to move item ${itemId} ${direction}:`, e);
+ alert(`Failed to move item. Check console for details.`);
+ }
+ }
+
+ async function removeQueueItem(itemId) {
+ if (!confirm(`Are you sure you want to remove "${itemId}" from the queue?`)) {
+ return;
+ }
+ try {
+ await fetch('/api/cascade_queue/remove', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ item_id: itemId })
+ });
+ await updateStats(); // Refresh queue after removal
+ } catch (e) {
+ console.error(`Failed to remove item ${itemId}:`, e);
+ alert(`Failed to remove item. Check console for details.`);
+ }
+ }
+ // --- END NEW QUEUE INTERACTION FUNCTIONS ---
+
setInterval(updateStats, 3000);
updateStats();
@@ -346,6 +524,262 @@ def do_POST(self):
self.wfile.write(
json.dumps({"error": f"Internal server error: {str(e)}"}).encode()
)
+ # NEW POST endpoint for updating Logic Memory
+ elif self.path == "/api/update_memory":
+ if self.controller is None:
+ self.send_response(503)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": "Controller not initialized"}).encode()
+ )
+ return
+
+ content_length = int(self.headers.get("Content-Length", 0))
+ post_data = self.rfile.read(content_length)
+ try:
+ data = json.loads(post_data.decode("utf-8"))
+ new_memory_content = data.get("content")
+
+ if new_memory_content is None:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {"error": "Missing 'content' in request body"}
+ ).encode()
+ )
+ return
+
+ self.controller.update_memory(new_memory_content)
+
+ self.send_response(200)
+ self.send_header("Content-type", "application/json")
+ self.send_header("Access-Control-Allow-Origin", "*")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {"message": "Logic Memory updated successfully"}
+ ).encode()
+ )
+
+ except json.JSONDecodeError:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(json.dumps({"error": "Invalid JSON"}).encode())
+ except AttributeError:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {
+ "error": "Controller method 'update_memory' not found. Ensure entrance.py is updated."
+ }
+ ).encode()
+ )
+ except Exception as e:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": f"Internal server error: {str(e)}"}).encode()
+ )
+ # NEW POST endpoint for moving cascade queue items
+ elif self.path == "/api/cascade_queue/move":
+ if self.controller is None:
+ self.send_response(503)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": "Controller not initialized"}).encode()
+ )
+ return
+
+ content_length = int(self.headers.get("Content-Length", 0))
+ post_data = self.rfile.read(content_length)
+ try:
+ data = json.loads(post_data.decode("utf-8"))
+ item_id = data.get("item_id")
+ direction = data.get("direction")
+
+ if not item_id or direction not in ["up", "down"]:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {
+ "error": "Missing 'item_id' or invalid 'direction' in request body"
+ }
+ ).encode()
+ )
+ return
+
+ self.controller.move_cascade_queue_item(item_id, direction)
+
+ self.send_response(200)
+ self.send_header("Content-type", "application/json")
+ self.send_header("Access-Control-Allow-Origin", "*")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {"message": f"Item {item_id} moved {direction} successfully"}
+ ).encode()
+ )
+
+ except json.JSONDecodeError:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(json.dumps({"error": "Invalid JSON"}).encode())
+ except AttributeError:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {
+ "error": "Controller method 'move_cascade_queue_item' not found. Ensure entrance.py is updated."
+ }
+ ).encode()
+ )
+ except Exception as e:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": f"Internal server error: {str(e)}"}).encode()
+ )
+
+ # NEW POST endpoint for removing cascade queue items
+ elif self.path == "/api/cascade_queue/remove":
+ if self.controller is None:
+ self.send_response(503)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": "Controller not initialized"}).encode()
+ )
+ return
+
+ content_length = int(self.headers.get("Content-Length", 0))
+ post_data = self.rfile.read(content_length)
+ try:
+ data = json.loads(post_data.decode("utf-8"))
+ item_id = data.get("item_id")
+
+ if not item_id:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {"error": "Missing 'item_id' in request body"}
+ ).encode()
+ )
+ return
+
+ self.controller.remove_cascade_queue_item(item_id)
+
+ self.send_response(200)
+ self.send_header("Content-type", "application/json")
+ self.send_header("Access-Control-Allow-Origin", "*")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {"message": f"Item {item_id} removed successfully"}
+ ).encode()
+ )
+
+ except json.JSONDecodeError:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(json.dumps({"error": "Invalid JSON"}).encode())
+ except AttributeError:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {
+ "error": "Controller method 'remove_cascade_queue_item' not found. Ensure entrance.py is updated."
+ }
+ ).encode()
+ )
+ except Exception as e:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": f"Internal server error: {str(e)}"}).encode()
+ )
+ # NEW POST endpoint for adding items to cascade queue
+ elif self.path == "/api/cascade_queue/add":
+ if self.controller is None:
+ self.send_response(503)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": "Controller not initialized"}).encode()
+ )
+ return
+
+ content_length = int(self.headers.get("Content-Length", 0))
+ post_data = self.rfile.read(content_length)
+ try:
+ data = json.loads(post_data.decode("utf-8"))
+ item = data.get("item")
+
+ if not item:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": "Missing 'item' in request body"}).encode()
+ )
+ return
+
+ self.controller.add_to_cascade_queue(item)
+
+ self.send_response(200)
+ self.send_header("Content-type", "application/json")
+ self.send_header("Access-Control-Allow-Origin", "*")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {
+ "message": f"Item '{item}' added to cascade queue successfully"
+ }
+ ).encode()
+ )
+
+ except json.JSONDecodeError:
+ self.send_response(400)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(json.dumps({"error": "Invalid JSON"}).encode())
+ except AttributeError:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps(
+ {
+ "error": "Controller method 'add_to_cascade_queue' not found. Ensure entrance.py is updated."
+ }
+ ).encode()
+ )
+ except Exception as e:
+ self.send_response(500)
+ self.send_header("Content-type", "application/json")
+ self.end_headers()
+ self.wfile.write(
+ json.dumps({"error": f"Internal server error: {str(e)}"}).encode()
+ )
else:
self.send_response(404)
self.end_headers()
diff --git a/src/pyob/pyob_launcher.py b/src/pyob/pyob_launcher.py
index 03c0535..0ff106a 100644
--- a/src/pyob/pyob_launcher.py
+++ b/src/pyob/pyob_launcher.py
@@ -10,6 +10,117 @@
DEFAULT_GEMINI_MODEL = "gemini-2.5-flash"
DEFAULT_LOCAL_MODEL = "qwen3-coder:30b"
+# New feature content: HTML and JavaScript for the Pending Patch Review Card.
+# This content is intended for OBSERVER_HTML, which is likely served by
+# pyob_dashboard.py or entrance.py. Storing it here as a string literal
+# in pyob_launcher.py makes it syntactically valid Python, but it would
+# require further integration in the web serving components (e.g.,
+# EntranceController or pyob_dashboard.py) to be functionally active
+# in the frontend UI.
+OBSERVER_PATCH_REVIEW_HTML = """
+
+
+
+
+"""
+
def load_config():
"""Load config from file or environment, or prompt user if missing."""