diff --git a/agent-node/src/agent_node/skills/shell_bridge.py b/agent-node/src/agent_node/skills/shell_bridge.py index cdf72e0..0879cde 100644 --- a/agent-node/src/agent_node/skills/shell_bridge.py +++ b/agent-node/src/agent_node/skills/shell_bridge.py @@ -166,6 +166,13 @@ print(f" [🐚⚠️] Protocol parsing failed: {e}") if sess.get("event"): sess["event"].set() + # Matches Windows ConHost title-set sequences: "0;......" lines + _WIN_TITLE_RE = re.compile(r'^\s*0;.*$', re.MULTILINE) + # Matches the 'call "...bat"' invocation line echoed by cmd + _BAT_CALL_RE = re.compile(r'^\s*call\s+".+\.bat".*$', re.MULTILINE | re.IGNORECASE) + # Matches the 'del /Q ...' cleanup echo + _DEL_RE = re.compile(r'^\s*del\s+/Q\s+".+\.bat".*$', re.MULTILINE | re.IGNORECASE) + def _handle_ui_streaming(self, sess, session_id, active_tid, decoded, on_event): """Internal helper to filter plumbing and stream terminal output to the client.""" # Clean framing echoes from the live stream @@ -174,9 +181,15 @@ decoded = STRIP_START_FENCE.sub('', decoded) decoded = STRIP_BRACKET_FENCE.sub('', decoded) - # Line-Aware Stealthing for extra safety - lines = decoded.splitlines(keepends=True) - clean_lines = [line for line in lines if not PROTOCOL_HINT_PATTERN.search(ANSI_ESCAPE.sub('', line))] + # Strip Windows internals: title-set escape sequences, bat call/del lines + clean_no_ansi = ANSI_ESCAPE.sub('', decoded) + clean_no_ansi = self._WIN_TITLE_RE.sub('', clean_no_ansi) + clean_no_ansi = self._BAT_CALL_RE.sub('', clean_no_ansi) + clean_no_ansi = self._DEL_RE.sub('', clean_no_ansi) + + # Line-Aware Stealthing for extra safety (protocol fence markers) + lines = clean_no_ansi.splitlines(keepends=True) + clean_lines = [line for line in lines if not PROTOCOL_HINT_PATTERN.search(line)] stealth_out = "".join(clean_lines) if stealth_out.strip(): @@ -270,7 +283,9 @@ with self.lock: sess["active_task"] = tid sess["event"] = event - sess["buffer_file"] = tempfile.NamedTemporaryFile("w+", encoding="utf-8", prefix=f"cortex_task_{tid}_", delete=False) + _tmp = tempfile.NamedTemporaryFile("w+", encoding="utf-8", prefix=f"cortex_task_{tid}_", delete=False, suffix=".tmp") + sess["buffer_file"] = _tmp + sess["buffer_file_path"] = _tmp.name sess["tail_buffer"] = "" sess["result"] = result_container sess["cancel_event"] = cancel_event @@ -304,16 +319,16 @@ os.makedirs(spool_dir, exist_ok=True) task_path = os.path.join(spool_dir, f"{tid}.bat") - # Use a robust wrapper that ensures the TaskEnd fence is ALWAYS printed + # Write bat with @echo off to suppress internal echo noise with open(task_path, "w", encoding="utf-8") as f: f.write(f"@echo off\r\n" f"echo [[1337;TaskStart;id={tid}]]\r\n" - f"rem Execute command and capture exit code\r\n" - f"cmd /c \"{cmd}\"\r\n" + f"{cmd}\r\n" f"set __ctx_err=%errorlevel%\r\n" f"echo [[1337;TaskEnd;id={tid};exit=%__ctx_err%]]\r\n" f"exit /b %__ctx_err%\r\n") - return f"call \"{task_path}\"\r\n" + # Store bat path on sess so we can delete it after task + return f"call \"{task_path}\" & del /Q \"{task_path}\"\r\n" else: return f"echo -e -n \"\\033]1337;TaskStart;id={tid}\\007\"; {cmd}; __ctx_exit=$?; echo -e -n \"\\033]1337;TaskEnd;id={tid};exit=$__ctx_exit\\007\"\n" @@ -338,9 +353,16 @@ with self.lock: if sess.get("active_task") == tid: if sess.get("buffer_file"): - try: sess["buffer_file"].close() + try: + sess["buffer_file"].close() except: pass + # Delete the temp buffer file + tmp_path = sess.get("buffer_file_path") + if tmp_path: + try: os.remove(tmp_path) + except: pass sess["buffer_file"] = None + sess["buffer_file_path"] = None sess["active_task"] = None if sess.get("event") == event: sess["event"] = None if sess.get("cancel_event") == cancel_event: sess["cancel_event"] = None diff --git a/scripts/start_agent.bat b/scripts/start_agent.bat new file mode 100644 index 0000000..05d84ad --- /dev/null +++ b/scripts/start_agent.bat @@ -0,0 +1,4 @@ +@echo off +cd /d C:\Users\axiey\.cortex\agent-node +set PYTHONPATH=src +python src\agent_node\main.py > out.log 2>&1