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:
+1
-23
@@ -1,12 +1,10 @@
|
|||||||
import atexit
|
import atexit
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
@@ -37,26 +35,6 @@ def _resolve_sandbox_image() -> str:
|
|||||||
return image
|
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
|
async def run_cli(args: Any) -> None: # noqa: PLR0915
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
@@ -200,7 +178,7 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915
|
|||||||
scan_config=scan_config,
|
scan_config=scan_config,
|
||||||
scan_id=args.run_name,
|
scan_id=args.run_name,
|
||||||
image=_resolve_sandbox_image(),
|
image=_resolve_sandbox_image(),
|
||||||
sources_path=_resolve_sources_path(args),
|
local_sources=getattr(args, "local_sources", None) or [],
|
||||||
tracer=tracer,
|
tracer=tracer,
|
||||||
interactive=bool(getattr(args, "interactive", False)),
|
interactive=bool(getattr(args, "interactive", False)),
|
||||||
)
|
)
|
||||||
|
|||||||
+28
-20
@@ -3,14 +3,12 @@ import asyncio
|
|||||||
import atexit
|
import atexit
|
||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from importlib.metadata import PackageNotFoundError
|
from importlib.metadata import PackageNotFoundError
|
||||||
from importlib.metadata import version as pkg_version
|
from importlib.metadata import version as pkg_version
|
||||||
from pathlib import Path
|
|
||||||
from typing import TYPE_CHECKING, Any, ClassVar
|
from typing import TYPE_CHECKING, Any, ClassVar
|
||||||
|
|
||||||
|
|
||||||
@@ -726,6 +724,10 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
self._scan_loop: asyncio.AbstractEventLoop | None = None
|
self._scan_loop: asyncio.AbstractEventLoop | None = None
|
||||||
self._scan_stop_event = threading.Event()
|
self._scan_stop_event = threading.Event()
|
||||||
self._scan_completed = threading.Event()
|
self._scan_completed = threading.Event()
|
||||||
|
# Captured by ``scan_target`` when the scan thread crashes; read
|
||||||
|
# by ``run_tui`` after ``run_async()`` returns so the user sees
|
||||||
|
# the traceback on stderr instead of just a silent UI hang.
|
||||||
|
self._scan_error: BaseException | None = None
|
||||||
|
|
||||||
self._spinner_frame_index: int = 0 # Current animation frame index
|
self._spinner_frame_index: int = 0 # Current animation frame index
|
||||||
self._sweep_num_squares: int = 6 # Number of squares in sweep animation
|
self._sweep_num_squares: int = 6 # Number of squares in sweep animation
|
||||||
@@ -743,18 +745,6 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
self._setup_cleanup_handlers()
|
self._setup_cleanup_handlers()
|
||||||
|
|
||||||
def _resolve_sources_path(self) -> Path:
|
|
||||||
local_sources = getattr(self.args, "local_sources", None) or []
|
|
||||||
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(self.args.run_name)
|
|
||||||
sources.mkdir(parents=True, exist_ok=True)
|
|
||||||
return sources
|
|
||||||
|
|
||||||
def _build_scan_config(self, args: argparse.Namespace) -> dict[str, Any]:
|
def _build_scan_config(self, args: argparse.Namespace) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"scan_id": args.run_name,
|
"scan_id": args.run_name,
|
||||||
@@ -1102,7 +1092,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
return self._merge_renderables(renderables)
|
return self._merge_renderables(renderables)
|
||||||
|
|
||||||
def _get_status_display_content(
|
def _get_status_display_content( # noqa: PLR0911
|
||||||
self, agent_id: str, agent_data: dict[str, Any]
|
self, agent_id: str, agent_data: dict[str, Any]
|
||||||
) -> tuple[Text | None, Text, bool]:
|
) -> tuple[Text | None, Text, bool]:
|
||||||
status = agent_data.get("status", "running")
|
status = agent_data.get("status", "running")
|
||||||
@@ -1141,6 +1131,16 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
keymap.append("Send message to retry", style="dim")
|
keymap.append("Send message to retry", style="dim")
|
||||||
return (text, keymap, False)
|
return (text, keymap, False)
|
||||||
|
|
||||||
|
if status == "failed":
|
||||||
|
error_msg = agent_data.get("error_message", "")
|
||||||
|
text = Text()
|
||||||
|
if error_msg:
|
||||||
|
text.append(error_msg, style="red")
|
||||||
|
else:
|
||||||
|
text.append("Scan failed", style="red")
|
||||||
|
self._stop_dot_animation()
|
||||||
|
return (text, Text(), False)
|
||||||
|
|
||||||
if status == "waiting":
|
if status == "waiting":
|
||||||
keymap = Text()
|
keymap = Text()
|
||||||
keymap.append("Send message to resume", style="dim")
|
keymap.append("Send message to resume", style="dim")
|
||||||
@@ -1409,13 +1409,12 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
try:
|
try:
|
||||||
if not self._scan_stop_event.is_set():
|
if not self._scan_stop_event.is_set():
|
||||||
image = load_settings().runtime.image or "strix-sandbox:latest"
|
image = load_settings().runtime.image or "strix-sandbox:latest"
|
||||||
sources_path = self._resolve_sources_path()
|
|
||||||
loop.run_until_complete(
|
loop.run_until_complete(
|
||||||
run_strix_scan(
|
run_strix_scan(
|
||||||
scan_config=self.scan_config,
|
scan_config=self.scan_config,
|
||||||
scan_id=self.scan_config["run_name"],
|
scan_id=self.scan_config["run_name"],
|
||||||
image=str(image),
|
image=str(image),
|
||||||
sources_path=sources_path,
|
local_sources=getattr(self.args, "local_sources", None) or [],
|
||||||
tracer=self.tracer,
|
tracer=self.tracer,
|
||||||
bus=self.bus,
|
bus=self.bus,
|
||||||
interactive=True,
|
interactive=True,
|
||||||
@@ -1424,12 +1423,15 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
except (KeyboardInterrupt, asyncio.CancelledError):
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
||||||
logger.info("Scan interrupted by user")
|
logger.info("Scan interrupted by user")
|
||||||
except (ConnectionError, TimeoutError):
|
except (ConnectionError, TimeoutError) as e:
|
||||||
logging.exception("Network error during scan")
|
logging.exception("Network error during scan")
|
||||||
except RuntimeError:
|
self._scan_error = e
|
||||||
|
except RuntimeError as e:
|
||||||
logging.exception("Runtime error during scan")
|
logging.exception("Runtime error during scan")
|
||||||
except Exception:
|
self._scan_error = e
|
||||||
|
except Exception as e:
|
||||||
logging.exception("Unexpected error during scan")
|
logging.exception("Unexpected error during scan")
|
||||||
|
self._scan_error = e
|
||||||
finally:
|
finally:
|
||||||
# Best-effort sandbox teardown if early setup failed
|
# Best-effort sandbox teardown if early setup failed
|
||||||
# before run_strix_scan's own ``finally`` ran.
|
# before run_strix_scan's own ``finally`` ran.
|
||||||
@@ -1995,3 +1997,9 @@ async def run_tui(args: argparse.Namespace) -> None:
|
|||||||
"""Run strix in interactive TUI mode with textual."""
|
"""Run strix in interactive TUI mode with textual."""
|
||||||
app = StrixTUIApp(args)
|
app = StrixTUIApp(args)
|
||||||
await app.run_async()
|
await app.run_async()
|
||||||
|
# Propagate scan-thread failures: ``app.run_async`` returns normally
|
||||||
|
# when the user quits (ctrl-q) regardless of whether the scan
|
||||||
|
# crashed. Without this re-raise, ``main.py`` would treat a failed
|
||||||
|
# scan as success and print the completion banner.
|
||||||
|
if app._scan_error is not None:
|
||||||
|
raise app._scan_error
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import contextlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import UTC, datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Literal
|
from typing import TYPE_CHECKING, Any, Literal
|
||||||
|
|
||||||
@@ -45,7 +46,7 @@ from strix.orchestration.filter import inject_messages_filter
|
|||||||
from strix.orchestration.hooks import StrixOrchestrationHooks
|
from strix.orchestration.hooks import StrixOrchestrationHooks
|
||||||
from strix.orchestration.run_loop import run_with_continuation
|
from strix.orchestration.run_loop import run_with_continuation
|
||||||
from strix.runtime import session_manager
|
from strix.runtime import session_manager
|
||||||
from strix.telemetry.logging import set_scan_id, setup_scan_logging
|
from strix.telemetry.logging import set_agent_id, set_scan_id, setup_scan_logging
|
||||||
|
|
||||||
|
|
||||||
#: Default ``max_turns`` budget passed to ``Runner.run``.
|
#: Default ``max_turns`` budget passed to ``Runner.run``.
|
||||||
@@ -173,7 +174,7 @@ async def run_strix_scan(
|
|||||||
scan_config: dict[str, Any],
|
scan_config: dict[str, Any],
|
||||||
scan_id: str | None = None,
|
scan_id: str | None = None,
|
||||||
image: str,
|
image: str,
|
||||||
sources_path: Path,
|
local_sources: list[dict[str, str]] | None = None,
|
||||||
tracer: Any | None = None,
|
tracer: Any | None = None,
|
||||||
bus: AgentMessageBus | None = None,
|
bus: AgentMessageBus | None = None,
|
||||||
interactive: bool = False,
|
interactive: bool = False,
|
||||||
@@ -192,7 +193,11 @@ async def run_strix_scan(
|
|||||||
should pass a stable id.
|
should pass a stable id.
|
||||||
image: Docker image tag for the sandbox (e.g.
|
image: Docker image tag for the sandbox (e.g.
|
||||||
``"strix-sandbox:0.2.0"``).
|
``"strix-sandbox:0.2.0"``).
|
||||||
sources_path: Host directory mounted into ``/workspace/sources``.
|
local_sources: Per-source mount specs from
|
||||||
|
:func:`strix.interface.utils.collect_local_sources` —
|
||||||
|
each entry's ``source_path`` (host) is bind-mounted at
|
||||||
|
``/workspace/<workspace_subdir>``. Pass ``None`` (or ``[]``)
|
||||||
|
for non-whitebox runs.
|
||||||
tracer: Optional Strix tracer. Stored in context for the
|
tracer: Optional Strix tracer. Stored in context for the
|
||||||
telemetry hook chain. Pass ``None`` for unit tests.
|
telemetry hook chain. Pass ``None`` for unit tests.
|
||||||
interactive: Renders the interactive-mode prompt block on the
|
interactive: Renders the interactive-mode prompt block on the
|
||||||
@@ -300,7 +305,7 @@ async def run_strix_scan(
|
|||||||
bundle = await session_manager.create_or_reuse(
|
bundle = await session_manager.create_or_reuse(
|
||||||
scan_id,
|
scan_id,
|
||||||
image=image,
|
image=image,
|
||||||
sources_path=sources_path,
|
local_sources=local_sources or [],
|
||||||
)
|
)
|
||||||
logger.info("Sandbox ready for scan %s", scan_id)
|
logger.info("Sandbox ready for scan %s", scan_id)
|
||||||
|
|
||||||
@@ -450,12 +455,27 @@ async def run_strix_scan(
|
|||||||
interactive=interactive,
|
interactive=interactive,
|
||||||
session=root_session,
|
session=root_session,
|
||||||
)
|
)
|
||||||
except BaseException:
|
except BaseException as exc:
|
||||||
logger.exception("Strix scan %s failed", scan_id)
|
logger.exception("Strix scan %s failed", scan_id)
|
||||||
# Cancel any descendant tasks the root spawned before unwinding.
|
# Cancel any descendant tasks the root spawned before unwinding.
|
||||||
# cancel_descendants is idempotent and handles the empty-tree case.
|
# cancel_descendants is idempotent and handles the empty-tree case.
|
||||||
if root_id is not None:
|
if root_id is not None:
|
||||||
await bus.cancel_descendants(root_id)
|
await bus.cancel_descendants(root_id)
|
||||||
|
# The SDK's on_agent_end hook only fires after a successful
|
||||||
|
# ``Runner.run_streamed`` reaches the agent's first turn. A
|
||||||
|
# failure earlier (e.g., model-provider routing, sandbox
|
||||||
|
# bring-up) leaves the root stuck at status="running" — the
|
||||||
|
# TUI keeps animating "Initializing" forever. Finalize it
|
||||||
|
# here so the bus + tracer reflect reality, and stash the
|
||||||
|
# error message for the status-line display.
|
||||||
|
error_message = f"{type(exc).__name__}: {exc}"
|
||||||
|
if tracer is not None and root_id in getattr(tracer, "agents", {}):
|
||||||
|
tracer.agents[root_id]["status"] = "failed"
|
||||||
|
tracer.agents[root_id]["error_message"] = error_message
|
||||||
|
tracer.agents[root_id]["updated_at"] = datetime.now(UTC).isoformat()
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
await bus.finalize(root_id, "failed")
|
||||||
|
set_agent_id(None)
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
for s in sessions_to_close:
|
for s in sessions_to_close:
|
||||||
|
|||||||
@@ -48,6 +48,13 @@ async def _docker_backend(
|
|||||||
NET_RAW caps + ``host.docker.internal`` host-gateway. Imports
|
NET_RAW caps + ``host.docker.internal`` host-gateway. Imports
|
||||||
``docker`` lazily so deployments that target a non-Docker
|
``docker`` lazily so deployments that target a non-Docker
|
||||||
backend don't need the docker-py library installed.
|
backend don't need the docker-py library installed.
|
||||||
|
|
||||||
|
``session.start()`` is what materializes the manifest entries
|
||||||
|
(LocalDir copies, mount setup, etc.) into the running container —
|
||||||
|
the SDK's ``client.create()`` only builds the inner session object
|
||||||
|
without applying the manifest. ``async with session:`` would call it
|
||||||
|
too, but Strix manages session lifetime explicitly via
|
||||||
|
``client.delete()`` so we trigger ``start()`` ourselves.
|
||||||
"""
|
"""
|
||||||
import docker
|
import docker
|
||||||
from agents.sandbox.sandboxes.docker import DockerSandboxClientOptions
|
from agents.sandbox.sandboxes.docker import DockerSandboxClientOptions
|
||||||
@@ -57,6 +64,7 @@ async def _docker_backend(
|
|||||||
client = StrixDockerSandboxClient(docker.from_env())
|
client = StrixDockerSandboxClient(docker.from_env())
|
||||||
options = DockerSandboxClientOptions(image=image, exposed_ports=exposed_ports)
|
options = DockerSandboxClientOptions(image=image, exposed_ports=exposed_ports)
|
||||||
session = await client.create(options=options, manifest=manifest)
|
session = await client.create(options=options, manifest=manifest)
|
||||||
|
await session.start()
|
||||||
return client, session
|
return client, session
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ next scan from starting.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from agents.sandbox.entries import LocalDir
|
from agents.sandbox.entries import BaseEntry, LocalDir
|
||||||
from agents.sandbox.manifest import Environment, Manifest
|
from agents.sandbox.manifest import Environment, Manifest
|
||||||
|
|
||||||
from strix.config import load_settings
|
from strix.config import load_settings
|
||||||
@@ -26,10 +27,6 @@ from strix.runtime.backends import get_backend
|
|||||||
from strix.runtime.caido_bootstrap import bootstrap_caido
|
from strix.runtime.caido_bootstrap import bootstrap_caido
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -48,15 +45,17 @@ async def create_or_reuse(
|
|||||||
scan_id: str,
|
scan_id: str,
|
||||||
*,
|
*,
|
||||||
image: str,
|
image: str,
|
||||||
sources_path: Path,
|
local_sources: list[dict[str, str]],
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Return the existing bundle for ``scan_id`` or create a new one.
|
"""Return the existing bundle for ``scan_id`` or create a new one.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
scan_id: Caller-provided scan identifier (used as cache key).
|
scan_id: Caller-provided scan identifier (used as cache key).
|
||||||
image: Docker image tag (e.g. ``"strix-sandbox:0.2.0"``).
|
image: Docker image tag (e.g. ``"strix-sandbox:0.2.0"``).
|
||||||
sources_path: Host directory mounted into the container's
|
local_sources: Each entry's ``source_path`` (host) is mounted at
|
||||||
``/workspace/sources`` so the agent can read user code.
|
``/workspace/<workspace_subdir>`` inside the container — the
|
||||||
|
same path the root-task prompt advertises. Empty list means
|
||||||
|
no host code is mounted (web/IP-only scans).
|
||||||
|
|
||||||
Returns the bundle dict containing ``client``, ``session``, and
|
Returns the bundle dict containing ``client``, ``session``, and
|
||||||
``caido_client``.
|
``caido_client``.
|
||||||
@@ -66,6 +65,19 @@ async def create_or_reuse(
|
|||||||
logger.info("Reusing existing sandbox session for scan %s", scan_id)
|
logger.info("Reusing existing sandbox session for scan %s", scan_id)
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
|
# Build Manifest entries keyed by ``workspace_subdir`` — the SDK
|
||||||
|
# mounts each at ``/workspace/<key>``, which is exactly the path
|
||||||
|
# ``_build_root_task`` puts in the agent's task prompt. Mounting
|
||||||
|
# only the listed source dirs (not their parent) avoids leaking
|
||||||
|
# unrelated host content into the sandbox.
|
||||||
|
entries: dict[str | Path, BaseEntry] = {}
|
||||||
|
for src in local_sources:
|
||||||
|
ws_subdir = src.get("workspace_subdir") or ""
|
||||||
|
host_path = src.get("source_path") or ""
|
||||||
|
if not ws_subdir or not host_path:
|
||||||
|
continue
|
||||||
|
entries[ws_subdir] = LocalDir(src=Path(host_path).expanduser().resolve())
|
||||||
|
|
||||||
# Caido runs as an in-container sidecar; HTTP(S) traffic from any
|
# Caido runs as an in-container sidecar; HTTP(S) traffic from any
|
||||||
# process started via ``session.exec`` (the SDK's Shell tool, etc.)
|
# process started via ``session.exec`` (the SDK's Shell tool, etc.)
|
||||||
# picks up these env vars automatically. ``NO_PROXY`` keeps the
|
# picks up these env vars automatically. ``NO_PROXY`` keeps the
|
||||||
@@ -73,7 +85,7 @@ async def create_or_reuse(
|
|||||||
# through Caido.
|
# through Caido.
|
||||||
container_caido_url = f"http://127.0.0.1:{_CONTAINER_CAIDO_PORT}"
|
container_caido_url = f"http://127.0.0.1:{_CONTAINER_CAIDO_PORT}"
|
||||||
manifest = Manifest(
|
manifest = Manifest(
|
||||||
entries={"sources": LocalDir(src=sources_path)},
|
entries=entries,
|
||||||
environment=Environment(
|
environment=Environment(
|
||||||
value={
|
value={
|
||||||
"PYTHONUNBUFFERED": "1",
|
"PYTHONUNBUFFERED": "1",
|
||||||
|
|||||||
Reference in New Issue
Block a user