4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
242 lines
8.0 KiB
Python
242 lines
8.0 KiB
Python
"""Safe read-only runtime metadata for sessions and sandboxed agent tools.
|
|
|
|
Populated at session init so agents can answer introspection questions
|
|
(e.g. OpenSRE version) without shelling out. Subprocess remains blocked in
|
|
the Python execution sandbox; this is the preferred alternative.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from config.config import get_environment
|
|
from config.version import get_opensre_version
|
|
|
|
# Reserved key merged into ``execute_python_code`` inputs (never overwrite user keys).
|
|
RUNTIME_INPUTS_KEY = "opensre_runtime"
|
|
|
|
_RELEASE_TAG_PATTERN = re.compile(r"^v\d+\.\d+(\.\d+){2,}$")
|
|
|
|
|
|
def _resolve_gitdir(candidate: Path) -> Path | None:
|
|
"""Return the git directory for ``candidate`` (``.git``), or ``None``.
|
|
|
|
Handles both a normal checkout (``.git`` is a directory) and a linked
|
|
worktree / submodule (``.git`` is a file with a ``gitdir: <path>`` line).
|
|
"""
|
|
if candidate.is_dir():
|
|
return candidate
|
|
if not candidate.is_file():
|
|
return None
|
|
try:
|
|
content = candidate.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return None
|
|
for line in content.splitlines():
|
|
stripped = line.strip()
|
|
if not stripped.startswith("gitdir:"):
|
|
continue
|
|
target = Path(stripped[len("gitdir:") :].strip())
|
|
if not target.is_absolute():
|
|
target = (candidate.parent / target).resolve()
|
|
return target if target.is_dir() else None
|
|
return None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class _GitLayout:
|
|
"""Per-worktree gitdir plus the shared common gitdir.
|
|
|
|
In a standard checkout the two are the same directory. In a linked worktree
|
|
(``git worktree add``), ``HEAD`` is per-worktree but ``refs/``, ``packed-refs``,
|
|
and tags live in the primary repo's gitdir named by the worktree's
|
|
``commondir`` marker file.
|
|
"""
|
|
|
|
gitdir: Path
|
|
commondir: Path
|
|
|
|
|
|
def _resolve_commondir(gitdir: Path) -> Path:
|
|
"""Return the shared common gitdir for ``gitdir``.
|
|
|
|
Standard checkouts have no ``commondir`` marker; the gitdir is its own
|
|
common dir. Linked worktrees carry a ``commondir`` file with a path
|
|
(relative to the per-worktree gitdir) to the primary repo's gitdir.
|
|
"""
|
|
marker = gitdir / "commondir"
|
|
if not marker.is_file():
|
|
return gitdir
|
|
try:
|
|
content = marker.read_text(encoding="utf-8").strip()
|
|
except OSError:
|
|
return gitdir
|
|
if not content:
|
|
return gitdir
|
|
target = Path(content)
|
|
if not target.is_absolute():
|
|
target = (gitdir / target).resolve()
|
|
return target if target.is_dir() else gitdir
|
|
|
|
|
|
def _find_git_layout() -> _GitLayout | None:
|
|
"""Walk up from this file to the enclosing repo's git layout."""
|
|
here = Path(__file__).resolve().parent
|
|
while here.parent != here:
|
|
gitdir = _resolve_gitdir(here / ".git")
|
|
if gitdir is not None:
|
|
return _GitLayout(gitdir=gitdir, commondir=_resolve_commondir(gitdir))
|
|
here = here.parent
|
|
return None
|
|
|
|
|
|
def _read_packed_refs(commondir: Path) -> dict[str, str]:
|
|
"""Parse ``<commondir>/packed-refs`` into a ``{ref_name: sha}`` map.
|
|
|
|
After ``git pack-refs`` the loose files under ``refs/`` disappear and both
|
|
branch heads and tag refs live only here. Peeled tag lines (``^<sha>``) are
|
|
ignored: the non-peeled line already holds the tag object's sha which is
|
|
enough for a build marker.
|
|
"""
|
|
packed = commondir / "packed-refs"
|
|
if not packed.is_file():
|
|
return {}
|
|
refs: dict[str, str] = {}
|
|
try:
|
|
content = packed.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return {}
|
|
for raw in content.splitlines():
|
|
line = raw.strip()
|
|
if not line or line.startswith(("#", "^")):
|
|
continue
|
|
sha, _, name = line.partition(" ")
|
|
if sha and name:
|
|
refs[name] = sha
|
|
return refs
|
|
|
|
|
|
def _read_ref_sha(layout: _GitLayout, ref_name: str) -> str | None:
|
|
"""Resolve ``ref_name`` (e.g. ``refs/heads/main``) via loose files + packed-refs.
|
|
|
|
Per-worktree refs (bisect/HEAD-like) may live under the worktree gitdir,
|
|
so it's tried first; branches and tags live in the commondir.
|
|
"""
|
|
for base in (layout.gitdir, layout.commondir):
|
|
loose = base / ref_name
|
|
if loose.is_file():
|
|
return loose.read_text(encoding="utf-8").strip() or None
|
|
return _read_packed_refs(layout.commondir).get(ref_name)
|
|
|
|
|
|
def _read_git_head_sha(layout: _GitLayout) -> str | None:
|
|
"""Short SHA the working tree currently points at, or ``None``."""
|
|
head_file = layout.gitdir / "HEAD"
|
|
if not head_file.is_file():
|
|
return None
|
|
head = head_file.read_text(encoding="utf-8").strip()
|
|
if not head.startswith("ref: "):
|
|
return head[:7] or None
|
|
sha = _read_ref_sha(layout, head[len("ref: ") :].strip())
|
|
return sha[:7] if sha else None
|
|
|
|
|
|
def _release_tag_sort_key(name: str) -> tuple[int, ...] | None:
|
|
"""Numeric tuple for a ``v0.1.YYYY.M.D`` tag; ``None`` if not all-numeric.
|
|
|
|
Numeric sort so ``v0.1.2026.10.1`` outranks ``v0.1.2026.9.30`` — a
|
|
lexicographic sort would pick the older tag because ``'9' > '1'`` as ASCII.
|
|
"""
|
|
parts = name.removeprefix("v").split(".")
|
|
try:
|
|
return tuple(int(part) for part in parts)
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def _iter_release_tag_names(commondir: Path) -> set[str]:
|
|
"""Release tag names, from loose refs and from ``packed-refs`` combined."""
|
|
names: set[str] = set()
|
|
tags_dir = commondir / "refs" / "tags"
|
|
if tags_dir.is_dir():
|
|
names.update(entry.name for entry in tags_dir.iterdir())
|
|
for ref_name in _read_packed_refs(commondir):
|
|
if ref_name.startswith("refs/tags/"):
|
|
names.add(ref_name[len("refs/tags/") :])
|
|
return names
|
|
|
|
|
|
def _read_latest_release_tag(commondir: Path) -> str | None:
|
|
"""Highest release tag (loose + packed) by numeric ordering."""
|
|
ranked: list[tuple[tuple[int, ...], str]] = []
|
|
for name in _iter_release_tag_names(commondir):
|
|
if not _RELEASE_TAG_PATTERN.match(name):
|
|
continue
|
|
key = _release_tag_sort_key(name)
|
|
if key is not None:
|
|
ranked.append((key, name))
|
|
if not ranked:
|
|
return None
|
|
ranked.sort(reverse=True)
|
|
return ranked[0][1]
|
|
|
|
|
|
def _detect_build_info() -> str:
|
|
"""Human-readable build marker: ``""`` for wheels, ``dev, <tag> @ <sha>`` for checkouts."""
|
|
layout = _find_git_layout()
|
|
if layout is None:
|
|
return ""
|
|
tag = _read_latest_release_tag(layout.commondir)
|
|
sha = _read_git_head_sha(layout)
|
|
if tag and sha:
|
|
return f"dev, {tag} @ {sha}"
|
|
if tag:
|
|
return f"dev, {tag}"
|
|
if sha:
|
|
return f"dev, @ {sha}"
|
|
return "dev"
|
|
|
|
|
|
def build_runtime_metadata() -> dict[str, Any]:
|
|
"""JSON-serializable read-only runtime facts for the current process.
|
|
|
|
Keys are stable for prompts and sandbox ``inputs``:
|
|
|
|
- ``opensre_version`` — package version via ``importlib.metadata``.
|
|
- ``opensre_build`` — ``""`` in released wheels; ``dev, v0.1.YYYY.M.D @ SHA``
|
|
in a git checkout so the LLM can quote the exact build in local dev.
|
|
- ``runtime_env`` — ``OPENSRE_ENV`` env var, else the app environment name.
|
|
"""
|
|
env_override = (os.environ.get("OPENSRE_ENV") or "").strip()
|
|
return {
|
|
"opensre_version": get_opensre_version(),
|
|
"opensre_build": _detect_build_info(),
|
|
"runtime_env": env_override or get_environment().value,
|
|
}
|
|
|
|
|
|
def merge_runtime_into_inputs(
|
|
inputs: dict[str, Any] | None,
|
|
*,
|
|
metadata: dict[str, Any] | None = None,
|
|
) -> dict[str, Any]:
|
|
"""Copy ``inputs`` and inject runtime metadata under :data:`RUNTIME_INPUTS_KEY`.
|
|
|
|
Never overwrites an existing ``opensre_runtime`` key supplied by the caller.
|
|
"""
|
|
merged: dict[str, Any] = dict(inputs or {})
|
|
if RUNTIME_INPUTS_KEY not in merged:
|
|
merged[RUNTIME_INPUTS_KEY] = dict(metadata or build_runtime_metadata())
|
|
return merged
|
|
|
|
|
|
__all__ = [
|
|
"RUNTIME_INPUTS_KEY",
|
|
"build_runtime_metadata",
|
|
"merge_runtime_into_inputs",
|
|
]
|