fix(runtime,interface): mount sources at advertised paths + surface scan failures in TUI

Two fixes that surfaced from a single broken run.

(1) Source mounting was double-broken:

- ``session_manager.create_or_reuse`` mounted the *parent* of the first
  local source under a hardcoded ``"sources"`` key, so the host's
  unrelated content leaked in at ``/workspace/sources/...`` while the
  agent's task prompt advertised ``/workspace/<workspace_subdir>``
  (from ``_build_root_task``). Result: the agent looked at
  ``/workspace/empty/`` (per the prompt), found nothing, and bailed.
- ``backends._docker_backend`` never called ``await session.start()``
  after ``client.create()`` — the SDK's manifest application
  (``LocalDir`` materialization, mount setup) only runs inside
  ``start()`` (or ``async with session:``). So even with the right
  ``entries`` the workspace would have been empty anyway.

Fix: thread ``args.local_sources`` (already populated by
``collect_local_sources``) all the way through to the session manager,
build ``Manifest.entries`` keyed by each source's ``workspace_subdir``,
and call ``session.start()`` in the docker backend so the SDK actually
materializes the entries. Drop the now-unused ``_resolve_sources_path``
helpers from ``cli.py`` and ``tui.py``.

(2) Scan-failure visibility was nonexistent in TUI mode:

- The SDK's ``on_agent_end`` hook only fires after the agent reaches its
  first turn. A failure earlier (model routing, sandbox bring-up, …)
  left the root agent stuck at ``status=running`` in the bus and
  tracer, so the TUI animated "Initializing" forever.
- ``scan_target`` in ``tui.py`` caught the exception and called
  ``logging.exception`` but never propagated it. ``run_tui`` returned
  cleanly when the user finally ctrl-q'd, so ``main.py`` happily
  printed the success-completion banner over a dead scan.

Fix: in ``run_strix_scan``'s ``except BaseException`` block, finalize
the root agent as ``"failed"`` in both the bus and the tracer (with the
error message attached). Capture the exception on
``StrixTUIApp._scan_error`` from the scan thread; ``run_tui`` re-raises
it after ``app.run_async()`` returns so ``main.py``'s existing handler
prints the traceback. Add a ``"failed"`` branch to
``_get_status_display_content`` that shows the error message in red,
mirroring the existing ``llm_failed`` branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
0xallam
2026-04-26 07:27:36 -07:00
co-authored by Claude Opus 4.7
parent 0518599f29
commit 53188a7583
5 changed files with 84 additions and 58 deletions
+1 -23
View File
@@ -1,12 +1,10 @@
import atexit
import contextlib
import logging
import os
import signal
import sys
import threading
import time
from pathlib import Path
from typing import Any
from rich.console import Console
@@ -37,26 +35,6 @@ def _resolve_sandbox_image() -> str:
return image
def _resolve_sources_path(args: Any) -> Path:
"""Pick the host directory to mount into ``/workspace/sources``.
- With ``--local-sources``, mount the parent of the first source so
the agent can walk down into the actual tree.
- Otherwise, a per-run scratch dir under ``$XDG_CACHE_HOME/strix``.
"""
local_sources: list[dict[str, str]] | None = getattr(args, "local_sources", None)
if local_sources:
first = local_sources[0]
host_path = first.get("host_path") or first.get("source_path") or first.get("path")
if host_path:
return Path(host_path).expanduser().resolve().parent
cache_root = os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache")
sources = Path(cache_root) / "strix" / "sources" / str(args.run_name)
sources.mkdir(parents=True, exist_ok=True)
return sources
async def run_cli(args: Any) -> None: # noqa: PLR0915
console = Console()
@@ -200,7 +178,7 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915
scan_config=scan_config,
scan_id=args.run_name,
image=_resolve_sandbox_image(),
sources_path=_resolve_sources_path(args),
local_sources=getattr(args, "local_sources", None) or [],
tracer=tracer,
interactive=bool(getattr(args, "interactive", False)),
)