simbiot-harness/venv/lib/python3.12/site-packages/strands/vended_tools/shell/shell.py
Simbiot Harness b60439152b Simbiot Harness v0.1: тактовая лестница на Strands + CQRS/ES-спина
- harness/es.py: append-only event store + проекции (образец — кооп-портал)
- harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу
- harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы
- harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе
  (глобальный asyncio-рантайм Strands не переживает 2+ create_harness)
- cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом)
- 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
2026-09-29 15:28:50 +00:00

79 lines
3.1 KiB
Python

"""Shell tool for executing commands through a sandbox.
Provides :func:`make_shell` (a factory for a stateless, sandbox-routed shell tool)
and :data:`shell` (the default instance that reads the sandbox from the agent at
call time). Each call runs in a fresh shell; state such as variables and the
working directory does not persist across calls.
The command runs in whichever shell the sandbox provides -- ``sh`` for the Docker
and local environments, the remote login shell over SSH -- so it must not rely on
shell-specific syntax.
"""
from __future__ import annotations
import json
from typing import TYPE_CHECKING
from ...sandbox.errors import SandboxTimeoutError
from ...tools.decorator import tool
from ...types.tools import ToolContext
from .types import SANDBOX_SHELL_DESCRIPTION, ShellExecutionError, ShellOutput
if TYPE_CHECKING:
from ...sandbox.base import Sandbox
from ...tools.decorator import DecoratedFunctionTool
_DEFAULT_TIMEOUT = 120
def make_shell(
*,
sandbox: Sandbox | None = None,
name: str = "shell",
description: str = SANDBOX_SHELL_DESCRIPTION,
) -> DecoratedFunctionTool:
"""Create a stateless, sandbox-routed shell tool.
If a ``sandbox`` is passed, it is bound at creation time. Otherwise the tool
reads the sandbox from ``tool_context.agent.sandbox`` at call time. Used by
sandbox implementations in :meth:`~strands.sandbox.base.Sandbox.get_tools`
and by users who want a customized shell tool.
Args:
sandbox: Sandbox to bind at creation. When ``None``, the agent's
configured sandbox is used at call time.
name: Tool name. Defaults to ``"shell"``.
description: Tool description shown to the model.
Returns:
A decorated tool that executes shell commands through the sandbox.
"""
@tool(name=name, description=description, context="tool_context")
async def shell_tool(command: str, tool_context: ToolContext, timeout: int = _DEFAULT_TIMEOUT) -> ShellOutput:
"""Executes a shell command and returns its output and exit code.
Args:
command: The shell command to execute.
tool_context: Injected by the framework. Not user-facing.
timeout: Timeout in seconds (default: 120).
"""
active = sandbox if sandbox is not None else tool_context.agent.sandbox
try:
result = await active.execute(command, timeout=timeout)
except SandboxTimeoutError as e:
# The model only sees str(e), so the partial output rides in the message with the success field names.
partial: ShellOutput = {"output": e.stdout, "error": e.stderr, "exit_code": 124}
e.args = (f"{e}\n{json.dumps(partial)}",)
raise
except Exception as e:
# ShellExecutionError subclasses RuntimeError, so prior handlers still match.
raise ShellExecutionError(str(e)) from e
return {"output": result.stdout, "error": result.stderr, "exit_code": result.exit_code}
return shell_tool
shell = make_shell()
"""Default shell tool. Reads the sandbox from the agent's context at call time."""