Start monitoring a background shell's output.
(cls, bash_id: str)
| 134 | |
| 135 | @classmethod |
| 136 | async def start_monitor(cls, bash_id: str) -> None: |
| 137 | """Start monitoring a background shell's output.""" |
| 138 | shell = cls.get(bash_id) |
| 139 | if not shell: |
| 140 | return |
| 141 | |
| 142 | async def monitor(): |
| 143 | try: |
| 144 | process = shell.process |
| 145 | # Continuously read output until process ends |
| 146 | while process.returncode is None: |
| 147 | try: |
| 148 | if process.stdout: |
| 149 | line = await asyncio.wait_for(process.stdout.readline(), timeout=0.1) |
| 150 | if line: |
| 151 | decoded_line = line.decode("utf-8", errors="replace").rstrip("\n") |
| 152 | shell.add_output(decoded_line) |
| 153 | else: |
| 154 | break |
| 155 | except asyncio.TimeoutError: |
| 156 | await asyncio.sleep(0.1) |
| 157 | continue |
| 158 | except Exception: |
| 159 | await asyncio.sleep(0.1) |
| 160 | continue |
| 161 | |
| 162 | # Process ended, wait for exit code |
| 163 | try: |
| 164 | returncode = await process.wait() |
| 165 | except Exception: |
| 166 | returncode = -1 |
| 167 | |
| 168 | shell.update_status(is_alive=False, exit_code=returncode) |
| 169 | |
| 170 | except Exception as e: |
| 171 | if bash_id in cls._shells: |
| 172 | cls._shells[bash_id].status = "error" |
| 173 | cls._shells[bash_id].add_output(f"Monitor error: {str(e)}") |
| 174 | finally: |
| 175 | if bash_id in cls._monitor_tasks: |
| 176 | del cls._monitor_tasks[bash_id] |
| 177 | |
| 178 | task = asyncio.create_task(monitor()) |
| 179 | cls._monitor_tasks[bash_id] = task |
| 180 | |
| 181 | @classmethod |
| 182 | def _cancel_monitor(cls, bash_id: str) -> None: |