Files
rengine/toolbox/runner.py
T
Yogesh OjhaandClaude Fable 5.1 0a69f3201c copy: state the fact, delete the explanation
Repo-wide sweep of every user-facing string: frontend components, routes,
config and stores; API details and route docs; shared definitions and
models; stage config help; report prose; MCP, toolbox, interest and
connector text; notification and worker messages.

Descriptions that explained, reassured, justified or restated their title
are deleted. Confirm dialogs name what is removed and stop. Failed actions
read "<Noun> not <verbed>", successes "<Noun> <verbed>". No reader address,
no product-as-subject, no em-dashes or parentheticals in prose.

Mirrors updated on both sides: EPSS band help, compare refusal reasons,
interest labels. Four tests asserting on old wording follow the new text.
The rules are condensed into CLAUDE.md under Writing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SK5o8xR96cbSnAfjYSTPSe
2026-09-12 11:35:07 +05:30

97 lines
3.0 KiB
Python

"""The one place a tool result becomes a stored run."""
from __future__ import annotations
from datetime import datetime
from pydantic import ValidationError
from shared.definitions.toolbox import Block, BlockKind, RunStatus, ToolRunRead
from shared.utils.datetime import utc_now
from shared.utils.text import strip_control
from toolbox.base import ToolError, ToolInput, ToolOutcome
from toolbox.registry import ToolSpec
MAX_ERROR_LENGTH = 400
QUEUE_DEADLINE_SECONDS = 120
RUN_DEADLINE_SECONDS = 420
def validate(spec: ToolSpec, payload: dict) -> ToolInput:
try:
return spec.tool_cls.Input.model_validate(payload)
except ValidationError as exc:
raise ToolError(_readable(exc)) from exc
def _readable(exc: ValidationError) -> str:
parts = []
for error in exc.errors()[:3]:
field = ".".join(str(p) for p in error.get("loc", ()) if p != "body")
message = error.get("msg", "is invalid")
parts.append(f"{field}: {message}" if field else message)
return ". ".join(parts) or "The input is not valid."
def _elapsed(run: ToolRunRead) -> int:
start = run.started_at or run.queued_at
try:
began = datetime.fromisoformat(start)
except (TypeError, ValueError):
return 0
return max(0, int((utc_now() - began).total_seconds() * 1000))
_SELF_CARRYING = frozenset({BlockKind.HERO.value, BlockKind.IMAGE.value})
def _worth_showing(block: Block) -> bool:
"""An empty block with no empty-state text is not rendered."""
if block.kind in _SELF_CARRYING:
return bool(block.headline or block.src)
if block.facts or block.rows or block.tags or block.text:
return True
return bool(block.empty)
def _age(stamp: str | None) -> float:
try:
return (utc_now() - datetime.fromisoformat(stamp or "")).total_seconds()
except (TypeError, ValueError):
return 0.0
def expire(run: ToolRunRead) -> ToolRunRead:
"""A run past its deadline is recorded as failed."""
if (
run.status == RunStatus.QUEUED.value
and _age(run.queued_at) > QUEUE_DEADLINE_SECONDS
):
return fail(run, "No worker accepted this run.")
if (
run.status == RunStatus.RUNNING.value
and _age(run.started_at) > RUN_DEADLINE_SECONDS
):
return fail(run, "The worker stopped reporting on this run.")
return run
def complete(run: ToolRunRead, outcome: ToolOutcome) -> ToolRunRead:
run.status = RunStatus.COMPLETED.value
run.summary = outcome.summary
run.blocks = [b for b in outcome.blocks if _worth_showing(b)]
run.caveats = outcome.caveats
run.pivot = outcome.pivot
run.raw = outcome.raw
run.finished_at = utc_now().isoformat()
run.duration_ms = _elapsed(run)
return run
def fail(run: ToolRunRead, message: str) -> ToolRunRead:
run.status = RunStatus.FAILED.value
run.error = strip_control(str(message))[:MAX_ERROR_LENGTH] or "The run failed."
run.finished_at = utc_now().isoformat()
run.duration_ms = _elapsed(run)
return run