chore: import upstream snapshot with attribution
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
"""Progress-tracker port (Protocol) + Noop default + injection helpers.
|
||||
|
||||
Core code (under ``core/domain/``, ``tools/investigation/``, ``utils/``)
|
||||
imports only from this module to report stage progress; the CLI
|
||||
surface implements the Protocol and registers its concrete tracker
|
||||
via :func:`set_progress_tracker` at boundary.
|
||||
|
||||
The Protocol surface is intentionally narrow: the methods listed here
|
||||
are the ones core actually calls. The Rich-backed REPL tracker exposes
|
||||
many more (subtext animation, ``print_above`` etc.), but those stay in
|
||||
the adapter — they're not the core's concern.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Protocol
|
||||
|
||||
|
||||
class ProgressReporter(Protocol):
|
||||
"""Stage-progress reporting contract for core code.
|
||||
|
||||
The default in a process that hasn't registered an adapter is
|
||||
:class:`NoopProgressTracker` — all methods are no-ops. The CLI
|
||||
layer registers a Rich-backed implementation at startup via
|
||||
:func:`set_progress_tracker`.
|
||||
"""
|
||||
|
||||
def start(self, node_name: str, message: str | None = None) -> None:
|
||||
"""Mark stage ``node_name`` as started, with an optional caption."""
|
||||
|
||||
def complete(
|
||||
self,
|
||||
node_name: str,
|
||||
fields_updated: list[str] | None = None,
|
||||
message: str | None = None,
|
||||
) -> None:
|
||||
"""Mark stage ``node_name`` as completed, optionally naming the
|
||||
state fields it produced and a closing caption.
|
||||
"""
|
||||
|
||||
def error(self, node_name: str, message: str) -> None:
|
||||
"""Mark stage ``node_name`` as errored with a human-readable cause."""
|
||||
|
||||
def record_tool_start(
|
||||
self,
|
||||
tool_name: str,
|
||||
tool_input: Any = None,
|
||||
*,
|
||||
event_key: str | None = None,
|
||||
) -> None:
|
||||
"""Record that ``tool_name`` has begun execution within the
|
||||
current stage. ``event_key`` is the per-invocation id used by
|
||||
the matching :meth:`record_tool_end` call.
|
||||
"""
|
||||
|
||||
def record_tool_end(
|
||||
self,
|
||||
tool_name: str,
|
||||
output: Any = None,
|
||||
*,
|
||||
event_key: str | None = None,
|
||||
tool_input: Any = None,
|
||||
) -> None:
|
||||
"""Record that ``tool_name`` finished, with optional output
|
||||
+ the same ``event_key`` passed to ``record_tool_start`` so
|
||||
the adapter can pair start/end for timing.
|
||||
"""
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Tear down any active display/animation/watchers.
|
||||
|
||||
Core callers invoke this when transitioning from progress
|
||||
rendering into final-report rendering, so the live display
|
||||
releases the terminal before plain text prints.
|
||||
"""
|
||||
|
||||
|
||||
class NoopProgressTracker:
|
||||
"""Drop-on-floor implementation used when no adapter is registered.
|
||||
|
||||
Conforms to :class:`ProgressReporter` structurally. Headless
|
||||
contexts (tests, non-TTY runs, scripted invocations) get this by
|
||||
default, so core code can call tracker methods unconditionally
|
||||
without checking for ``None``.
|
||||
"""
|
||||
|
||||
def start(self, node_name: str, message: str | None = None) -> None:
|
||||
_ = (node_name, message)
|
||||
|
||||
def complete(
|
||||
self,
|
||||
node_name: str,
|
||||
fields_updated: list[str] | None = None,
|
||||
message: str | None = None,
|
||||
) -> None:
|
||||
_ = (node_name, fields_updated, message)
|
||||
|
||||
def error(self, node_name: str, message: str) -> None:
|
||||
_ = (node_name, message)
|
||||
|
||||
def record_tool_start(
|
||||
self,
|
||||
tool_name: str,
|
||||
tool_input: Any = None,
|
||||
*,
|
||||
event_key: str | None = None,
|
||||
) -> None:
|
||||
_ = (tool_name, tool_input, event_key)
|
||||
|
||||
def record_tool_end(
|
||||
self,
|
||||
tool_name: str,
|
||||
output: Any = None,
|
||||
*,
|
||||
event_key: str | None = None,
|
||||
tool_input: Any = None,
|
||||
) -> None:
|
||||
_ = (tool_name, output, event_key, tool_input)
|
||||
|
||||
def stop(self) -> None:
|
||||
return None
|
||||
|
||||
|
||||
_tracker: ProgressReporter = NoopProgressTracker()
|
||||
_tracker_factory: Callable[[], ProgressReporter] | None = None
|
||||
_silenced: bool = False
|
||||
|
||||
|
||||
def get_progress_tracker() -> ProgressReporter:
|
||||
"""Return the currently-registered tracker (or the Noop default).
|
||||
|
||||
When a CLI factory is registered and progress has not been silenced,
|
||||
the first call materializes the adapter lazily so ``ProgressReporter``
|
||||
is constructed after REPL boot (when ``_repl_progress_active()`` is
|
||||
accurate) rather than at process start-up.
|
||||
"""
|
||||
global _tracker
|
||||
if not _silenced and isinstance(_tracker, NoopProgressTracker) and _tracker_factory is not None:
|
||||
set_progress_tracker(_tracker_factory())
|
||||
return _tracker
|
||||
|
||||
|
||||
def set_progress_tracker_factory(factory: Callable[[], ProgressReporter] | None) -> None:
|
||||
"""Register a lazy factory for the CLI progress tracker.
|
||||
|
||||
Boundary code (typically ``install_product_adapters``)
|
||||
installs ``get_tracker`` here instead of constructing the Rich
|
||||
tracker eagerly at process start-up.
|
||||
"""
|
||||
global _tracker_factory
|
||||
_tracker_factory = factory
|
||||
|
||||
|
||||
def set_progress_tracker(tracker: ProgressReporter) -> None:
|
||||
"""Install ``tracker`` as the active implementation. Called by the
|
||||
CLI boundary (or any other adapter) at startup so subsequent
|
||||
``get_progress_tracker()`` calls return the real implementation.
|
||||
"""
|
||||
global _tracker, _silenced
|
||||
_tracker = tracker
|
||||
if not isinstance(tracker, NoopProgressTracker):
|
||||
_silenced = False
|
||||
|
||||
|
||||
def silence_progress_tracker() -> None:
|
||||
"""Replace whatever is registered with a Noop tracker.
|
||||
|
||||
Used at the pipeline entry point when a run shouldn't emit progress
|
||||
(e.g. headless investigations whose output goes only to a sink).
|
||||
Calls ``stop()`` on the previous tracker first so any active
|
||||
display / watcher / animation it owns gets released — silencing a
|
||||
Rich tracker without stopping it leaks its toggle watcher and the
|
||||
Live display keeps holding the terminal.
|
||||
"""
|
||||
global _silenced
|
||||
_silenced = True
|
||||
_tracker.stop()
|
||||
set_progress_tracker(NoopProgressTracker())
|
||||
Reference in New Issue
Block a user