Manager for all background shell processes.
| 106 | |
| 107 | |
| 108 | class BackgroundShellManager: |
| 109 | """Manager for all background shell processes.""" |
| 110 | |
| 111 | _shells: dict[str, BackgroundShell] = {} |
| 112 | _monitor_tasks: dict[str, asyncio.Task] = {} |
| 113 | |
| 114 | @classmethod |
| 115 | def add(cls, shell: BackgroundShell) -> None: |
| 116 | """Add a background shell to management.""" |
| 117 | cls._shells[shell.bash_id] = shell |
| 118 | |
| 119 | @classmethod |
| 120 | def get(cls, bash_id: str) -> BackgroundShell | None: |
| 121 | """Get a background shell by ID.""" |
| 122 | return cls._shells.get(bash_id) |
| 123 | |
| 124 | @classmethod |
| 125 | def get_available_ids(cls) -> list[str]: |
| 126 | """Get all available bash IDs.""" |
| 127 | return list(cls._shells.keys()) |
| 128 | |
| 129 | @classmethod |
| 130 | def _remove(cls, bash_id: str) -> None: |
| 131 | """Remove a background shell from management (internal use only).""" |
| 132 | if bash_id in cls._shells: |
| 133 | del cls._shells[bash_id] |
| 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: |
nothing calls this directly
no outgoing calls
no test coverage detected