diff --git a/poc-grpc-agent/agent_node/core/watcher.py b/poc-grpc-agent/agent_node/core/watcher.py index 8ddee21..1e0207c 100644 --- a/poc-grpc-agent/agent_node/core/watcher.py +++ b/poc-grpc-agent/agent_node/core/watcher.py @@ -4,6 +4,7 @@ import hashlib from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler +from shared_core.ignore import CortexIgnore from protos import agent_pb2 class SyncEventHandler(FileSystemEventHandler): @@ -12,7 +13,9 @@ self.session_id = session_id self.root_path = root_path self.callback = callback + self.ignore_filter = CortexIgnore(root_path) self.last_sync = {} # path -> last_hash + self.locked = False def on_modified(self, event): if not event.is_directory: @@ -27,9 +30,17 @@ self._process_change(event.dest_path) def _process_change(self, abs_path): - rel_path = os.path.relpath(abs_path, self.root_path) - # Basic ignore filter (v1) - if any(part.startswith('.') for part in rel_path.split(os.sep)) or "node_modules" in rel_path: + if self.locked: + return # Block all user edits when session is locked + + rel_path = os.path.normpath(os.path.relpath(abs_path, self.root_path)) + + # Phase 3: Dynamic reload if .cortexignore / .gitignore changed + if rel_path in [".cortexignore", ".gitignore"]: + print(f" [*] Reloading Ignore Filter for {self.session_id}") + self.ignore_filter = CortexIgnore(self.root_path) + + if self.ignore_filter.is_ignored(rel_path): return try: @@ -63,7 +74,12 @@ """Manages FS observers for active synchronization.""" def __init__(self, callback): self.callback = callback - self.observers = {} # session_id -> observer + self.observers = {} # session_id -> (observer, handler) + + def set_lock(self, session_id, locked=True): + if session_id in self.observers: + print(f"[*] Workspace LOCK for {session_id}: {locked}") + self.observers[session_id][1].locked = locked def start_watching(self, session_id, root_path): if session_id in self.observers: @@ -74,13 +90,14 @@ observer = Observer() observer.schedule(handler, root_path, recursive=True) observer.start() - self.observers[session_id] = observer + self.observers[session_id] = (observer, handler) def stop_watching(self, session_id): if session_id in self.observers: print(f"[*] Stopping Watcher for Session {session_id}") - self.observers[session_id].stop() - self.observers[session_id].join() + obs, _ = self.observers[session_id] + obs.stop() + obs.join() del self.observers[session_id] def shutdown(self): diff --git a/poc-grpc-agent/agent_node/node.py b/poc-grpc-agent/agent_node/node.py index c7fab99..d547ad6 100644 --- a/poc-grpc-agent/agent_node/node.py +++ b/poc-grpc-agent/agent_node/node.py @@ -149,6 +149,36 @@ self.watcher.start_watching(sid, watch_path) elif ctrl.action == agent_pb2.SyncControl.STOP_WATCHING: self.watcher.stop_watching(sid) + elif ctrl.action == agent_pb2.SyncControl.LOCK: + self.watcher.set_lock(sid, True) + elif ctrl.action == agent_pb2.SyncControl.UNLOCK: + self.watcher.set_lock(sid, False) + elif ctrl.action == agent_pb2.SyncControl.REFRESH_MANIFEST: + # Node -> Server Manifest Push + self._push_full_manifest(sid, ctrl.path) + + def _push_full_manifest(self, session_id, rel_path="."): + """Pushes the current local manifest back to the server.""" + print(f" [📁📤] Pushing Full Manifest for {session_id}") + watch_path = rel_path if os.path.isabs(rel_path) else os.path.join(self.sync_mgr.get_session_dir(session_id), rel_path) + + # We need a manifest generator similar to GhostMirrorManager but on the node + # For Phase 3, we'll implement a simple one here + files = [] + for root, dirs, filenames in os.walk(watch_path): + for filename in filenames: + abs_path = os.path.join(root, filename) + r_path = os.path.relpath(abs_path, watch_path) + with open(abs_path, "rb") as f: + h = hashlib.sha256(f.read()).hexdigest() + files.append(agent_pb2.FileInfo(path=r_path, size=os.path.getsize(abs_path), hash=h)) + + self.task_queue.put(agent_pb2.ClientTaskMessage( + file_sync=agent_pb2.FileSyncMessage( + session_id=session_id, + manifest=agent_pb2.DirectoryManifest(root_path=rel_path, files=files) + ) + )) def _handle_task(self, task): print(f"[*] Task Launch: {task.task_id}", flush=True) diff --git a/poc-grpc-agent/orchestrator/app.py b/poc-grpc-agent/orchestrator/app.py index 30d40b3..34ac451 100644 --- a/poc-grpc-agent/orchestrator/app.py +++ b/poc-grpc-agent/orchestrator/app.py @@ -72,16 +72,23 @@ # Start watching only on the first node to test broadcast to others orch.assistant.control_sync(active_nodes[0], "test-session-001", action="START") + # Phase 3: LOCK Test (Simulate an AI edit phase where user edits are blocked) + time.sleep(10) + print("\n[🔒] Orchestrator: Locking Node 0 to prevent user interference (Phase 3)...") + orch.assistant.lock_workspace(active_nodes[0], "test-session-001") + # Phase 4: Browser Bridge print("\n[🧠] AI Phase 4: Navigating Browser (Antigravity Bridge)...") nav_action = agent_pb2.BrowserAction( - action=agent_pb2.BrowserAction.NAVIGATE, - url="https://example.com", - session_id="antigravity-session-1" + action=agent_pb2.BrowserAction.NAVIGATE, + url="https://google.com", + session_id="br-session-456" ) - res_nav = orch.assistant.dispatch_browser(target_node, nav_action) - print(f" Nav Result: {res_nav}") + res_browser = orch.assistant.dispatch_browser(target_node, nav_action) + print(f" Browser Result: {res_browser}", flush=True) + # Stay alive for diagnostics + time.sleep(60) # Phase 4 Pro: Perception & Evaluation print("\n[🧠] AI Phase 4 Pro: Perception & Advanced Logic...") a11y_action = agent_pb2.BrowserAction( diff --git a/poc-grpc-agent/orchestrator/core/mirror.py b/poc-grpc-agent/orchestrator/core/mirror.py index 6af816a..0a60bb3 100644 --- a/poc-grpc-agent/orchestrator/core/mirror.py +++ b/poc-grpc-agent/orchestrator/core/mirror.py @@ -2,6 +2,7 @@ import shutil import hashlib from typing import Dict, List +from shared_core.ignore import CortexIgnore from protos import agent_pb2 class GhostMirrorManager: @@ -11,6 +12,10 @@ if not os.path.exists(self.storage_root): os.makedirs(self.storage_root, exist_ok=True) + def get_ignore_filter(self, session_id: str) -> CortexIgnore: + """Returns a CortexIgnore instance for a session.""" + return CortexIgnore(self.get_workspace_path(session_id)) + def get_workspace_path(self, session_id: str) -> str: """Returns the local absolute path for a session's mirror.""" path = os.path.join(self.storage_root, session_id) @@ -20,6 +25,13 @@ def write_file_chunk(self, session_id: str, file_payload: agent_pb2.FilePayload): """Writes a chunk of data to the local mirror.""" workspace = self.get_workspace_path(session_id) + + # Phase 3 ignore filter + ignore_filter = self.get_ignore_filter(session_id) + if ignore_filter.is_ignored(file_payload.path): + print(f" [📁🚷] Ignoring write to {file_payload.path}") + return + # Prevent path traversal safe_path = os.path.normpath(os.path.join(workspace, file_payload.path)) if not safe_path.startswith(workspace): @@ -37,12 +49,19 @@ def generate_manifest(self, session_id: str) -> agent_pb2.DirectoryManifest: """Generates a manifest of the current local mirror state.""" workspace = self.get_workspace_path(session_id) + ignore_filter = self.get_ignore_filter(session_id) files = [] - for root, _, filenames in os.walk(workspace): + for root, dirs, filenames in os.walk(workspace): + # Efficiently prune skipped directories + dirs[:] = [d for d in dirs if not ignore_filter.is_ignored(os.path.relpath(os.path.join(root, d), workspace))] + for filename in filenames: abs_path = os.path.join(root, filename) rel_path = os.path.relpath(abs_path, workspace) + if ignore_filter.is_ignored(rel_path): + continue + with open(abs_path, "rb") as f: file_hash = hashlib.sha256(f.read()).hexdigest() diff --git a/poc-grpc-agent/orchestrator/services/assistant.py b/poc-grpc-agent/orchestrator/services/assistant.py index 693c8f8..fad8643 100644 --- a/poc-grpc-agent/orchestrator/services/assistant.py +++ b/poc-grpc-agent/orchestrator/services/assistant.py @@ -82,12 +82,37 @@ ) )) + def lock_workspace(self, node_id, session_id): + """Disables user-side synchronization from a node during AI refactors.""" + self.control_sync(node_id, session_id, action="LOCK") + + def unlock_workspace(self, node_id, session_id): + """Re-enables user-side synchronization from a node.""" + self.control_sync(node_id, session_id, action="UNLOCK") + + def request_manifest(self, node_id, session_id, path="."): + """Requests a full directory manifest from a node for drift checking.""" + node = self.registry.get_node(node_id) + if not node: return + node["queue"].put(agent_pb2.ServerTaskMessage( + file_sync=agent_pb2.FileSyncMessage( + session_id=session_id, + control=agent_pb2.SyncControl(action=agent_pb2.SyncControl.REFRESH_MANIFEST, path=path) + ) + )) + def control_sync(self, node_id, session_id, action="START", path="."): - """Sends a SyncControl command to a node (e.g. START_WATCHING).""" + """Sends a SyncControl command to a node (e.g. START_WATCHING, LOCK).""" node = self.registry.get_node(node_id) if not node: return - proto_action = agent_pb2.SyncControl.START_WATCHING if action == "START" else agent_pb2.SyncControl.STOP_WATCHING + action_map = { + "START": agent_pb2.SyncControl.START_WATCHING, + "STOP": agent_pb2.SyncControl.STOP_WATCHING, + "LOCK": agent_pb2.SyncControl.LOCK, + "UNLOCK": agent_pb2.SyncControl.UNLOCK + } + proto_action = action_map.get(action, agent_pb2.SyncControl.START_WATCHING) node["queue"].put(agent_pb2.ServerTaskMessage( file_sync=agent_pb2.FileSyncMessage( diff --git a/poc-grpc-agent/phase3_test_v2.txt b/poc-grpc-agent/phase3_test_v2.txt new file mode 100644 index 0000000..8d6c508 --- /dev/null +++ b/poc-grpc-agent/phase3_test_v2.txt @@ -0,0 +1 @@ +python3: can't open file '/app/poc-grpc-agent/poc-grpc-agent/test_mesh.py': [Errno 2] No such file or directory diff --git a/poc-grpc-agent/protos/agent.proto b/poc-grpc-agent/protos/agent.proto index 64a0813..adb9d8b 100644 --- a/poc-grpc-agent/protos/agent.proto +++ b/poc-grpc-agent/protos/agent.proto @@ -203,6 +203,9 @@ enum Action { START_WATCHING = 0; STOP_WATCHING = 1; + LOCK = 2; // Server -> Node: Disable user-side edits + UNLOCK = 3; // Server -> Node: Enable user-side edits + REFRESH_MANIFEST = 4; // Server -> Node: Request a full manifest from node } Action action = 1; string path = 2; diff --git a/poc-grpc-agent/protos/agent_pb2.py b/poc-grpc-agent/protos/agent_pb2.py index 338fb99..691a6ee 100644 --- a/poc-grpc-agent/protos/agent_pb2.py +++ b/poc-grpc-agent/protos/agent_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12protos/agent.proto\x12\x05\x61gent\"\xde\x01\n\x13RegistrationRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x12\n\nauth_token\x18\x03 \x01(\t\x12\x18\n\x10node_description\x18\x04 \x01(\t\x12\x42\n\x0c\x63\x61pabilities\x18\x05 \x03(\x0b\x32,.agent.RegistrationRequest.CapabilitiesEntry\x1a\x33\n\x11\x43\x61pabilitiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc5\x01\n\rSandboxPolicy\x12\'\n\x04mode\x18\x01 \x01(\x0e\x32\x19.agent.SandboxPolicy.Mode\x12\x18\n\x10\x61llowed_commands\x18\x02 \x03(\t\x12\x17\n\x0f\x64\x65nied_commands\x18\x03 \x03(\t\x12\x1a\n\x12sensitive_commands\x18\x04 \x03(\t\x12\x18\n\x10working_dir_jail\x18\x05 \x01(\t\"\"\n\x04Mode\x12\n\n\x06STRICT\x10\x00\x12\x0e\n\nPERMISSIVE\x10\x01\"x\n\x14RegistrationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12\x12\n\nsession_id\x18\x03 \x01(\t\x12$\n\x06policy\x18\x04 \x01(\x0b\x32\x14.agent.SandboxPolicy\"\xff\x01\n\x11\x43lientTaskMessage\x12,\n\rtask_response\x18\x01 \x01(\x0b\x32\x13.agent.TaskResponseH\x00\x12-\n\ntask_claim\x18\x02 \x01(\x0b\x32\x17.agent.TaskClaimRequestH\x00\x12,\n\rbrowser_event\x18\x03 \x01(\x0b\x32\x13.agent.BrowserEventH\x00\x12\'\n\x08\x61nnounce\x18\x04 \x01(\x0b\x32\x13.agent.NodeAnnounceH\x00\x12+\n\tfile_sync\x18\x05 \x01(\x0b\x32\x16.agent.FileSyncMessageH\x00\x42\t\n\x07payload\"\x1f\n\x0cNodeAnnounce\x12\x0f\n\x07node_id\x18\x01 \x01(\t\"\x87\x01\n\x0c\x42rowserEvent\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12,\n\x0b\x63onsole_msg\x18\x02 \x01(\x0b\x32\x15.agent.ConsoleMessageH\x00\x12,\n\x0bnetwork_req\x18\x03 \x01(\x0b\x32\x15.agent.NetworkRequestH\x00\x42\x07\n\x05\x65vent\"\x8d\x02\n\x11ServerTaskMessage\x12*\n\x0ctask_request\x18\x01 \x01(\x0b\x32\x12.agent.TaskRequestH\x00\x12\x31\n\x10work_pool_update\x18\x02 \x01(\x0b\x32\x15.agent.WorkPoolUpdateH\x00\x12\x30\n\x0c\x63laim_status\x18\x03 \x01(\x0b\x32\x18.agent.TaskClaimResponseH\x00\x12/\n\x0btask_cancel\x18\x04 \x01(\x0b\x32\x18.agent.TaskCancelRequestH\x00\x12+\n\tfile_sync\x18\x05 \x01(\x0b\x32\x16.agent.FileSyncMessageH\x00\x42\t\n\x07payload\"$\n\x11TaskCancelRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\"\xbd\x01\n\x0bTaskRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x11\n\ttask_type\x18\x02 \x01(\t\x12\x16\n\x0cpayload_json\x18\x03 \x01(\tH\x00\x12.\n\x0e\x62rowser_action\x18\x07 \x01(\x0b\x32\x14.agent.BrowserActionH\x00\x12\x12\n\ntimeout_ms\x18\x04 \x01(\x05\x12\x10\n\x08trace_id\x18\x05 \x01(\t\x12\x11\n\tsignature\x18\x06 \x01(\tB\t\n\x07payload\"\xa0\x02\n\rBrowserAction\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.agent.BrowserAction.ActionType\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\x12\x0c\n\x04text\x18\x04 \x01(\t\x12\x12\n\nsession_id\x18\x05 \x01(\t\x12\t\n\x01x\x18\x06 \x01(\x05\x12\t\n\x01y\x18\x07 \x01(\x05\"\x86\x01\n\nActionType\x12\x0c\n\x08NAVIGATE\x10\x00\x12\t\n\x05\x43LICK\x10\x01\x12\x08\n\x04TYPE\x10\x02\x12\x0e\n\nSCREENSHOT\x10\x03\x12\x0b\n\x07GET_DOM\x10\x04\x12\t\n\x05HOVER\x10\x05\x12\n\n\x06SCROLL\x10\x06\x12\t\n\x05\x43LOSE\x10\x07\x12\x08\n\x04\x45VAL\x10\x08\x12\x0c\n\x08GET_A11Y\x10\t\"\xe0\x02\n\x0cTaskResponse\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12*\n\x06status\x18\x02 \x01(\x0e\x32\x1a.agent.TaskResponse.Status\x12\x0e\n\x06stdout\x18\x03 \x01(\t\x12\x0e\n\x06stderr\x18\x04 \x01(\t\x12\x10\n\x08trace_id\x18\x05 \x01(\t\x12\x35\n\tartifacts\x18\x06 \x03(\x0b\x32\".agent.TaskResponse.ArtifactsEntry\x12\x30\n\x0e\x62rowser_result\x18\x07 \x01(\x0b\x32\x16.agent.BrowserResponseH\x00\x1a\x30\n\x0e\x41rtifactsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"<\n\x06Status\x12\x0b\n\x07SUCCESS\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x0b\n\x07TIMEOUT\x10\x02\x12\r\n\tCANCELLED\x10\x03\x42\x08\n\x06result\"\xdc\x01\n\x0f\x42rowserResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x10\n\x08snapshot\x18\x03 \x01(\x0c\x12\x13\n\x0b\x64om_content\x18\x04 \x01(\t\x12\x11\n\ta11y_tree\x18\x05 \x01(\t\x12\x13\n\x0b\x65val_result\x18\x06 \x01(\t\x12.\n\x0f\x63onsole_history\x18\x07 \x03(\x0b\x32\x15.agent.ConsoleMessage\x12.\n\x0fnetwork_history\x18\x08 \x03(\x0b\x32\x15.agent.NetworkRequest\"C\n\x0e\x43onsoleMessage\x12\r\n\x05level\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x14\n\x0ctimestamp_ms\x18\x03 \x01(\x03\"h\n\x0eNetworkRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\x05\x12\x15\n\rresource_type\x18\x04 \x01(\t\x12\x12\n\nlatency_ms\x18\x05 \x01(\x03\",\n\x0eWorkPoolUpdate\x12\x1a\n\x12\x61vailable_task_ids\x18\x01 \x03(\t\"4\n\x10TaskClaimRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\t\"E\n\x11TaskClaimResponse\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0f\n\x07granted\x18\x02 \x01(\x08\x12\x0e\n\x06reason\x18\x03 \x01(\t\"\xc1\x01\n\tHeartbeat\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x19\n\x11\x63pu_usage_percent\x18\x02 \x01(\x02\x12\x1c\n\x14memory_usage_percent\x18\x03 \x01(\x02\x12\x1b\n\x13\x61\x63tive_worker_count\x18\x04 \x01(\x05\x12\x1b\n\x13max_worker_capacity\x18\x05 \x01(\x05\x12\x16\n\x0estatus_message\x18\x06 \x01(\t\x12\x18\n\x10running_task_ids\x18\x07 \x03(\t\"-\n\x13HealthCheckResponse\x12\x16\n\x0eserver_time_ms\x18\x01 \x01(\x03\"\xd3\x01\n\x0f\x46ileSyncMessage\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12,\n\x08manifest\x18\x02 \x01(\x0b\x32\x18.agent.DirectoryManifestH\x00\x12\'\n\tfile_data\x18\x03 \x01(\x0b\x32\x12.agent.FilePayloadH\x00\x12#\n\x06status\x18\x04 \x01(\x0b\x32\x11.agent.SyncStatusH\x00\x12%\n\x07\x63ontrol\x18\x05 \x01(\x0b\x32\x12.agent.SyncControlH\x00\x42\t\n\x07payload\"w\n\x0bSyncControl\x12)\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x19.agent.SyncControl.Action\x12\x0c\n\x04path\x18\x02 \x01(\t\"/\n\x06\x41\x63tion\x12\x12\n\x0eSTART_WATCHING\x10\x00\x12\x11\n\rSTOP_WATCHING\x10\x01\"F\n\x11\x44irectoryManifest\x12\x11\n\troot_path\x18\x01 \x01(\t\x12\x1e\n\x05\x66iles\x18\x02 \x03(\x0b\x32\x0f.agent.FileInfo\"D\n\x08\x46ileInfo\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x03\x12\x0c\n\x04hash\x18\x03 \x01(\t\x12\x0e\n\x06is_dir\x18\x04 \x01(\x08\"_\n\x0b\x46ilePayload\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\r\n\x05\x63hunk\x18\x02 \x01(\x0c\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\x05\x12\x10\n\x08is_final\x18\x04 \x01(\x08\x12\x0c\n\x04hash\x18\x05 \x01(\t\"\x87\x01\n\nSyncStatus\x12$\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x16.agent.SyncStatus.Code\x12\x0f\n\x07message\x18\x02 \x01(\t\"B\n\x04\x43ode\x12\x06\n\x02OK\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x16\n\x12RECONCILE_REQUIRED\x10\x02\x12\x0f\n\x0bIN_PROGRESS\x10\x03\x32\xe9\x01\n\x11\x41gentOrchestrator\x12L\n\x11SyncConfiguration\x12\x1a.agent.RegistrationRequest\x1a\x1b.agent.RegistrationResponse\x12\x44\n\nTaskStream\x12\x18.agent.ClientTaskMessage\x1a\x18.agent.ServerTaskMessage(\x01\x30\x01\x12@\n\x0cReportHealth\x12\x10.agent.Heartbeat\x1a\x1a.agent.HealthCheckResponse(\x01\x30\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12protos/agent.proto\x12\x05\x61gent\"\xde\x01\n\x13RegistrationRequest\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x0f\n\x07version\x18\x02 \x01(\t\x12\x12\n\nauth_token\x18\x03 \x01(\t\x12\x18\n\x10node_description\x18\x04 \x01(\t\x12\x42\n\x0c\x63\x61pabilities\x18\x05 \x03(\x0b\x32,.agent.RegistrationRequest.CapabilitiesEntry\x1a\x33\n\x11\x43\x61pabilitiesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc5\x01\n\rSandboxPolicy\x12\'\n\x04mode\x18\x01 \x01(\x0e\x32\x19.agent.SandboxPolicy.Mode\x12\x18\n\x10\x61llowed_commands\x18\x02 \x03(\t\x12\x17\n\x0f\x64\x65nied_commands\x18\x03 \x03(\t\x12\x1a\n\x12sensitive_commands\x18\x04 \x03(\t\x12\x18\n\x10working_dir_jail\x18\x05 \x01(\t\"\"\n\x04Mode\x12\n\n\x06STRICT\x10\x00\x12\x0e\n\nPERMISSIVE\x10\x01\"x\n\x14RegistrationResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12\x12\n\nsession_id\x18\x03 \x01(\t\x12$\n\x06policy\x18\x04 \x01(\x0b\x32\x14.agent.SandboxPolicy\"\xff\x01\n\x11\x43lientTaskMessage\x12,\n\rtask_response\x18\x01 \x01(\x0b\x32\x13.agent.TaskResponseH\x00\x12-\n\ntask_claim\x18\x02 \x01(\x0b\x32\x17.agent.TaskClaimRequestH\x00\x12,\n\rbrowser_event\x18\x03 \x01(\x0b\x32\x13.agent.BrowserEventH\x00\x12\'\n\x08\x61nnounce\x18\x04 \x01(\x0b\x32\x13.agent.NodeAnnounceH\x00\x12+\n\tfile_sync\x18\x05 \x01(\x0b\x32\x16.agent.FileSyncMessageH\x00\x42\t\n\x07payload\"\x1f\n\x0cNodeAnnounce\x12\x0f\n\x07node_id\x18\x01 \x01(\t\"\x87\x01\n\x0c\x42rowserEvent\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12,\n\x0b\x63onsole_msg\x18\x02 \x01(\x0b\x32\x15.agent.ConsoleMessageH\x00\x12,\n\x0bnetwork_req\x18\x03 \x01(\x0b\x32\x15.agent.NetworkRequestH\x00\x42\x07\n\x05\x65vent\"\x8d\x02\n\x11ServerTaskMessage\x12*\n\x0ctask_request\x18\x01 \x01(\x0b\x32\x12.agent.TaskRequestH\x00\x12\x31\n\x10work_pool_update\x18\x02 \x01(\x0b\x32\x15.agent.WorkPoolUpdateH\x00\x12\x30\n\x0c\x63laim_status\x18\x03 \x01(\x0b\x32\x18.agent.TaskClaimResponseH\x00\x12/\n\x0btask_cancel\x18\x04 \x01(\x0b\x32\x18.agent.TaskCancelRequestH\x00\x12+\n\tfile_sync\x18\x05 \x01(\x0b\x32\x16.agent.FileSyncMessageH\x00\x42\t\n\x07payload\"$\n\x11TaskCancelRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\"\xbd\x01\n\x0bTaskRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x11\n\ttask_type\x18\x02 \x01(\t\x12\x16\n\x0cpayload_json\x18\x03 \x01(\tH\x00\x12.\n\x0e\x62rowser_action\x18\x07 \x01(\x0b\x32\x14.agent.BrowserActionH\x00\x12\x12\n\ntimeout_ms\x18\x04 \x01(\x05\x12\x10\n\x08trace_id\x18\x05 \x01(\t\x12\x11\n\tsignature\x18\x06 \x01(\tB\t\n\x07payload\"\xa0\x02\n\rBrowserAction\x12/\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x1f.agent.BrowserAction.ActionType\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x10\n\x08selector\x18\x03 \x01(\t\x12\x0c\n\x04text\x18\x04 \x01(\t\x12\x12\n\nsession_id\x18\x05 \x01(\t\x12\t\n\x01x\x18\x06 \x01(\x05\x12\t\n\x01y\x18\x07 \x01(\x05\"\x86\x01\n\nActionType\x12\x0c\n\x08NAVIGATE\x10\x00\x12\t\n\x05\x43LICK\x10\x01\x12\x08\n\x04TYPE\x10\x02\x12\x0e\n\nSCREENSHOT\x10\x03\x12\x0b\n\x07GET_DOM\x10\x04\x12\t\n\x05HOVER\x10\x05\x12\n\n\x06SCROLL\x10\x06\x12\t\n\x05\x43LOSE\x10\x07\x12\x08\n\x04\x45VAL\x10\x08\x12\x0c\n\x08GET_A11Y\x10\t\"\xe0\x02\n\x0cTaskResponse\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12*\n\x06status\x18\x02 \x01(\x0e\x32\x1a.agent.TaskResponse.Status\x12\x0e\n\x06stdout\x18\x03 \x01(\t\x12\x0e\n\x06stderr\x18\x04 \x01(\t\x12\x10\n\x08trace_id\x18\x05 \x01(\t\x12\x35\n\tartifacts\x18\x06 \x03(\x0b\x32\".agent.TaskResponse.ArtifactsEntry\x12\x30\n\x0e\x62rowser_result\x18\x07 \x01(\x0b\x32\x16.agent.BrowserResponseH\x00\x1a\x30\n\x0e\x41rtifactsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"<\n\x06Status\x12\x0b\n\x07SUCCESS\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x0b\n\x07TIMEOUT\x10\x02\x12\r\n\tCANCELLED\x10\x03\x42\x08\n\x06result\"\xdc\x01\n\x0f\x42rowserResponse\x12\x0b\n\x03url\x18\x01 \x01(\t\x12\r\n\x05title\x18\x02 \x01(\t\x12\x10\n\x08snapshot\x18\x03 \x01(\x0c\x12\x13\n\x0b\x64om_content\x18\x04 \x01(\t\x12\x11\n\ta11y_tree\x18\x05 \x01(\t\x12\x13\n\x0b\x65val_result\x18\x06 \x01(\t\x12.\n\x0f\x63onsole_history\x18\x07 \x03(\x0b\x32\x15.agent.ConsoleMessage\x12.\n\x0fnetwork_history\x18\x08 \x03(\x0b\x32\x15.agent.NetworkRequest\"C\n\x0e\x43onsoleMessage\x12\r\n\x05level\x18\x01 \x01(\t\x12\x0c\n\x04text\x18\x02 \x01(\t\x12\x14\n\x0ctimestamp_ms\x18\x03 \x01(\x03\"h\n\x0eNetworkRequest\x12\x0e\n\x06method\x18\x01 \x01(\t\x12\x0b\n\x03url\x18\x02 \x01(\t\x12\x0e\n\x06status\x18\x03 \x01(\x05\x12\x15\n\rresource_type\x18\x04 \x01(\t\x12\x12\n\nlatency_ms\x18\x05 \x01(\x03\",\n\x0eWorkPoolUpdate\x12\x1a\n\x12\x61vailable_task_ids\x18\x01 \x03(\t\"4\n\x10TaskClaimRequest\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0f\n\x07node_id\x18\x02 \x01(\t\"E\n\x11TaskClaimResponse\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x0f\n\x07granted\x18\x02 \x01(\x08\x12\x0e\n\x06reason\x18\x03 \x01(\t\"\xc1\x01\n\tHeartbeat\x12\x0f\n\x07node_id\x18\x01 \x01(\t\x12\x19\n\x11\x63pu_usage_percent\x18\x02 \x01(\x02\x12\x1c\n\x14memory_usage_percent\x18\x03 \x01(\x02\x12\x1b\n\x13\x61\x63tive_worker_count\x18\x04 \x01(\x05\x12\x1b\n\x13max_worker_capacity\x18\x05 \x01(\x05\x12\x16\n\x0estatus_message\x18\x06 \x01(\t\x12\x18\n\x10running_task_ids\x18\x07 \x03(\t\"-\n\x13HealthCheckResponse\x12\x16\n\x0eserver_time_ms\x18\x01 \x01(\x03\"\xd3\x01\n\x0f\x46ileSyncMessage\x12\x12\n\nsession_id\x18\x01 \x01(\t\x12,\n\x08manifest\x18\x02 \x01(\x0b\x32\x18.agent.DirectoryManifestH\x00\x12\'\n\tfile_data\x18\x03 \x01(\x0b\x32\x12.agent.FilePayloadH\x00\x12#\n\x06status\x18\x04 \x01(\x0b\x32\x11.agent.SyncStatusH\x00\x12%\n\x07\x63ontrol\x18\x05 \x01(\x0b\x32\x12.agent.SyncControlH\x00\x42\t\n\x07payload\"\xa3\x01\n\x0bSyncControl\x12)\n\x06\x61\x63tion\x18\x01 \x01(\x0e\x32\x19.agent.SyncControl.Action\x12\x0c\n\x04path\x18\x02 \x01(\t\"[\n\x06\x41\x63tion\x12\x12\n\x0eSTART_WATCHING\x10\x00\x12\x11\n\rSTOP_WATCHING\x10\x01\x12\x08\n\x04LOCK\x10\x02\x12\n\n\x06UNLOCK\x10\x03\x12\x14\n\x10REFRESH_MANIFEST\x10\x04\"F\n\x11\x44irectoryManifest\x12\x11\n\troot_path\x18\x01 \x01(\t\x12\x1e\n\x05\x66iles\x18\x02 \x03(\x0b\x32\x0f.agent.FileInfo\"D\n\x08\x46ileInfo\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\x03\x12\x0c\n\x04hash\x18\x03 \x01(\t\x12\x0e\n\x06is_dir\x18\x04 \x01(\x08\"_\n\x0b\x46ilePayload\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\r\n\x05\x63hunk\x18\x02 \x01(\x0c\x12\x13\n\x0b\x63hunk_index\x18\x03 \x01(\x05\x12\x10\n\x08is_final\x18\x04 \x01(\x08\x12\x0c\n\x04hash\x18\x05 \x01(\t\"\x87\x01\n\nSyncStatus\x12$\n\x04\x63ode\x18\x01 \x01(\x0e\x32\x16.agent.SyncStatus.Code\x12\x0f\n\x07message\x18\x02 \x01(\t\"B\n\x04\x43ode\x12\x06\n\x02OK\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x12\x16\n\x12RECONCILE_REQUIRED\x10\x02\x12\x0f\n\x0bIN_PROGRESS\x10\x03\x32\xe9\x01\n\x11\x41gentOrchestrator\x12L\n\x11SyncConfiguration\x12\x1a.agent.RegistrationRequest\x1a\x1b.agent.RegistrationResponse\x12\x44\n\nTaskStream\x12\x18.agent.ClientTaskMessage\x1a\x18.agent.ServerTaskMessage(\x01\x30\x01\x12@\n\x0cReportHealth\x12\x10.agent.Heartbeat\x1a\x1a.agent.HealthCheckResponse(\x01\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -75,20 +75,20 @@ _globals['_HEALTHCHECKRESPONSE']._serialized_end=2963 _globals['_FILESYNCMESSAGE']._serialized_start=2966 _globals['_FILESYNCMESSAGE']._serialized_end=3177 - _globals['_SYNCCONTROL']._serialized_start=3179 - _globals['_SYNCCONTROL']._serialized_end=3298 - _globals['_SYNCCONTROL_ACTION']._serialized_start=3251 - _globals['_SYNCCONTROL_ACTION']._serialized_end=3298 - _globals['_DIRECTORYMANIFEST']._serialized_start=3300 - _globals['_DIRECTORYMANIFEST']._serialized_end=3370 - _globals['_FILEINFO']._serialized_start=3372 - _globals['_FILEINFO']._serialized_end=3440 - _globals['_FILEPAYLOAD']._serialized_start=3442 - _globals['_FILEPAYLOAD']._serialized_end=3537 - _globals['_SYNCSTATUS']._serialized_start=3540 - _globals['_SYNCSTATUS']._serialized_end=3675 - _globals['_SYNCSTATUS_CODE']._serialized_start=3609 - _globals['_SYNCSTATUS_CODE']._serialized_end=3675 - _globals['_AGENTORCHESTRATOR']._serialized_start=3678 - _globals['_AGENTORCHESTRATOR']._serialized_end=3911 + _globals['_SYNCCONTROL']._serialized_start=3180 + _globals['_SYNCCONTROL']._serialized_end=3343 + _globals['_SYNCCONTROL_ACTION']._serialized_start=3252 + _globals['_SYNCCONTROL_ACTION']._serialized_end=3343 + _globals['_DIRECTORYMANIFEST']._serialized_start=3345 + _globals['_DIRECTORYMANIFEST']._serialized_end=3415 + _globals['_FILEINFO']._serialized_start=3417 + _globals['_FILEINFO']._serialized_end=3485 + _globals['_FILEPAYLOAD']._serialized_start=3487 + _globals['_FILEPAYLOAD']._serialized_end=3582 + _globals['_SYNCSTATUS']._serialized_start=3585 + _globals['_SYNCSTATUS']._serialized_end=3720 + _globals['_SYNCSTATUS_CODE']._serialized_start=3654 + _globals['_SYNCSTATUS_CODE']._serialized_end=3720 + _globals['_AGENTORCHESTRATOR']._serialized_start=3723 + _globals['_AGENTORCHESTRATOR']._serialized_end=3956 # @@protoc_insertion_point(module_scope) diff --git a/poc-grpc-agent/shared_core/__init__.py b/poc-grpc-agent/shared_core/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/poc-grpc-agent/shared_core/__init__.py diff --git a/poc-grpc-agent/shared_core/ignore.py b/poc-grpc-agent/shared_core/ignore.py new file mode 100644 index 0000000..c3f0cb5 --- /dev/null +++ b/poc-grpc-agent/shared_core/ignore.py @@ -0,0 +1,38 @@ +import os +import fnmatch + +class CortexIgnore: + """Handles .cortexignore (and .gitignore) pattern matching.""" + def __init__(self, root_path): + self.root_path = root_path + self.patterns = self._load_patterns() + + def _load_patterns(self): + patterns = [".git", "node_modules", ".cortex_sync", "__pycache__", "*.pyc"] # Default ignores + ignore_file = os.path.join(self.root_path, ".cortexignore") + if not os.path.exists(ignore_file): + ignore_file = os.path.join(self.root_path, ".gitignore") + + if os.path.exists(ignore_file): + with open(ignore_file, "r") as f: + for line in f: + line = line.strip() + if line and not line.startswith("#"): + patterns.append(line) + return patterns + + def is_ignored(self, rel_path): + """Returns True if the path matches any ignore pattern.""" + for pattern in self.patterns: + # Handle directory patterns + if pattern.endswith("/"): + if rel_path.startswith(pattern) or f"/{pattern}" in f"/{rel_path}": + return True + # Standard glob matching + if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(os.path.basename(rel_path), pattern): + return True + # Handle nested matches + for part in rel_path.split(os.sep): + if fnmatch.fnmatch(part, pattern): + return True + return False diff --git a/poc-grpc-agent/test_mesh.py b/poc-grpc-agent/test_mesh.py deleted file mode 100644 index ecb8cc2..0000000 --- a/poc-grpc-agent/test_mesh.py +++ /dev/null @@ -1,85 +0,0 @@ - -import time -import subprocess -import os -import signal - -def run_mesh_test(): - print("[🚀] Starting Collaborative Mesh Test...") - print("[🛡️] Orchestrator: Starting...") - - # 1. Start Orchestrator - orchestrator = subprocess.Popen( - ["python3", "-m", "orchestrator.app"], - cwd="/app/poc-grpc-agent", - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1 - ) - - time.sleep(3) # Wait for start - - print("[🤖] Node Alpha: Starting...") - # 2. Start Agent Node 1 - node1 = subprocess.Popen( - ["python3", "-m", "agent_node.main"], - cwd="/app/poc-grpc-agent", - env={**os.environ, "AGENT_NODE_ID": "node-alpha", "CORTEX_SYNC_DIR": "/tmp/cortex-sync-alpha"}, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1 - ) - - print("[🤖] Node Beta: Starting...") - # 3. Start Agent Node 2 - node2 = subprocess.Popen( - ["python3", "-m", "agent_node.main"], - cwd="/app/poc-grpc-agent", - env={**os.environ, "AGENT_NODE_ID": "node-beta", "CORTEX_SYNC_DIR": "/tmp/cortex-sync-beta"}, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - bufsize=1 - ) - - print("[⏳] Running simulation for 40 seconds...") - start_time = time.time() - - # Simple thread to print outputs in real-time - import threading - def pipe_output(name, pipe): - for line in pipe: - print(f"[{name}] {line.strip()}") - - threading.Thread(target=pipe_output, args=("ORCH", orchestrator.stdout), daemon=True).start() - threading.Thread(target=pipe_output, args=("N1", node1.stdout), daemon=True).start() - threading.Thread(target=pipe_output, args=("N2", node2.stdout), daemon=True).start() - - # Simulate a local edit on Node Alpha (N1) after a delay to test real-time sync - def simulate_local_edit(): - time.sleep(22) # Wait for initial push and START_WATCHING - sync_file = "/tmp/cortex-sync-alpha/test-session-001/hello.py" - print(f"\n[📝] User Sim: Editing {sync_file} locally on Node Alpha...") - with open(sync_file, "a") as f: - f.write("\n# BROADCAST TEST: User added this line on Alpha!\n") - - threading.Thread(target=simulate_local_edit, daemon=True).start() - - time.sleep(40) - - # 4. Cleanup - print("\n[🛑] Test Finished. Terminating processes...") - orchestrator.terminate() - node1.terminate() - node2.terminate() - - time.sleep(2) - orchestrator.kill() - node1.kill() - node2.kill() - print("[✅] Done.") - -if __name__ == "__main__": - run_mesh_test()