MCPcopy Create free account
hub / github.com/ScriptedAlchemy/tracedecay / _spawn_dashboard

Function _spawn_dashboard

dashboard/hermes-wrapper/plugin_api.py:209–292  ·  view source on GitHub ↗

Starts ``tracedecay dashboard`` and returns its base URL.

()

Source from the content-addressed store, hash-verified

207
208
209def _spawn_dashboard() -> str:
210 """Starts ``tracedecay dashboard`` and returns its base URL."""
211 binary = _find_tracedecay_bin()
212 if not binary:
213 raise HTTPException(
214 status_code=503,
215 detail=(
216 "tracedecay binary not found. Install tracedecay or set "
217 "TRACEDECAY_BIN / TRACEDECAY_DASHBOARD_URL."
218 ),
219 )
220 project = _project_root()
221 cmd = [
222 binary,
223 "dashboard",
224 "--host",
225 "127.0.0.1",
226 "--port",
227 "0",
228 "--path",
229 project,
230 ]
231 # Spawned on the dedicated long-lived thread so PDEATHSIG binds the
232 # child's lifetime to the Hermes process, not a transient request thread.
233 process = _spawn_pool.submit(
234 subprocess.Popen,
235 cmd,
236 stdout=subprocess.PIPE,
237 stderr=subprocess.PIPE,
238 text=True,
239 env=_dashboard_env(),
240 preexec_fn=_child_preexec if _libc is not None else None, # noqa: PLW1509 — minimal prctl-only hook
241 ).result(timeout=_SPAWN_TIMEOUT_SECONDS)
242
243 # Single reader per pipe, for the child's whole lifetime: the stderr
244 # drain keeps a bounded tail for error detail; the stdout reader parses
245 # the URL line then KEEPS draining (a stopped reader would eventually
246 # block the server on a full pipe buffer and 502 every proxied request).
247 stderr_tail: deque = deque(maxlen=_STDERR_TAIL_LINES)
248 threading.Thread(
249 target=_drain_pipe, args=(process.stderr, stderr_tail), daemon=True
250 ).start()
251
252 # The first stdout line is stable: "tracedecay dashboard listening on <url>".
253 # Extract the URL itself rather than depending on any surrounding text.
254 url_ready = threading.Event()
255 url_holder: dict[str, str] = {}
256
257 def _read_stdout() -> None:
258 if process.stdout is None:
259 url_ready.set()
260 return
261 for line in process.stdout:
262 stripped = line.strip()
263 if not url_ready.is_set() and "listening on" in stripped:
264 match = _LISTENING_URL_RE.search(stripped)
265 if match:
266 url_holder["url"] = match.group(0).rstrip("/")

Callers 1

_upstream_baseFunction · 0.85

Calls 7

_find_tracedecay_binFunction · 0.85
_project_rootFunction · 0.85
_dashboard_envFunction · 0.85
_terminate_processFunction · 0.85
_wait_until_readyFunction · 0.85
listFunction · 0.50
infoMethod · 0.45

Tested by

no test coverage detected