sbxloop 0.7.3__tar.gz → 0.7.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sbxloop-0.7.3 → sbxloop-0.7.4}/PKG-INFO +1 -1
- sbxloop-0.7.3/src/sbxloop/_vendor/sbxloop_worker-0.7.3-py3-none-any.whl → sbxloop-0.7.4/src/sbxloop/_vendor/sbxloop_worker-0.7.4-py3-none-any.whl +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/_version.py +2 -2
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/audits.py +29 -10
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/loop.py +11 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/.gitignore +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/README.md +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/hatch_build.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/pyproject.toml +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/cli/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/cli/app.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/cli/doctor.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/cli/models.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/cli/tui.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/config.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/backlog.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/control.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/discord.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/discord_format.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/github.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/model.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/paths.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/postmortem.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/sources.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/daemon/store.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/deliver.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/engine.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/model.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/phases.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/prompts/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/prompts/decompose.md +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/prompts/execute.md +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/prompts/plan.md +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/prompts/scrutinize.md +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/prompts/steer.md +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/prompts/validate.md +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/engine/store.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/errors.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/events.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/gc.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/gh/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/gh/ops.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/gh/reporter.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/hostgit.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/ids.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/policy.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/py.typed +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/bake.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/cli.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/conformance.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/models.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/pair.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/parse.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/provision.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/prune.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/sandbox.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/sbx/secretstate.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/toolchains.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/verifylint.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/worker/__init__.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/worker/client.py +0 -0
- {sbxloop-0.7.3 → sbxloop-0.7.4}/src/sbxloop/worker/wheel.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sbxloop
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Agentic loop orchestration on Docker Sandboxes (sbx) with isolated credential domains
|
|
5
5
|
Project-URL: Homepage, https://github.com/brettbergin/sbxloop
|
|
6
6
|
Project-URL: Repository, https://github.com/brettbergin/sbxloop
|
|
Binary file
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 4)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -21,6 +21,11 @@ from pathlib import Path
|
|
|
21
21
|
AUDIT_DIR = Path(".github") / "sbxloop" / "audits"
|
|
22
22
|
AUDIT_TITLE_PREFIX = "audit: "
|
|
23
23
|
_FRONT_MATTER = re.compile(r"\A---\s*\n(.*?)\n---\s*\n?", re.S)
|
|
24
|
+
# Preferred metadata form: an HTML comment. Invisible on GitHub and left
|
|
25
|
+
# alone by every Markdown formatter (mdformat rewrote a `---` YAML block
|
|
26
|
+
# into a thematic break + an H2 the first time the charters shipped, and
|
|
27
|
+
# the daemon then saw no metadata at all).
|
|
28
|
+
_COMMENT_META = re.compile(r"\A\s*<!--\s*sbxloop(?:-audit)?\s*:?\s*(.*?)\s*-->\s*\n?", re.S)
|
|
24
29
|
_EVERY = re.compile(r"^\s*(\d+(?:\.\d+)?)\s*([dhm])\s*$", re.I)
|
|
25
30
|
_UNIT_S = {"d": 86_400.0, "h": 3_600.0, "m": 60.0}
|
|
26
31
|
_NAME_OK = re.compile(r"^[a-z0-9][a-z0-9._-]{0,63}$")
|
|
@@ -49,21 +54,35 @@ def parse_every(text: str) -> float:
|
|
|
49
54
|
|
|
50
55
|
|
|
51
56
|
def parse_charter(path: Path, rel: str | None = None) -> Charter:
|
|
52
|
-
"""One charter file.
|
|
53
|
-
(default true)
|
|
54
|
-
|
|
57
|
+
"""One charter file. Metadata keys: ``every`` (required), ``enabled``
|
|
58
|
+
(default true), given as ``<!-- sbxloop: every=7d -->`` on the first line
|
|
59
|
+
(preferred) or a ``---`` front-matter block. The name is the file stem,
|
|
60
|
+
which also keys the schedule and the issue title — keep it stable."""
|
|
55
61
|
name = path.stem
|
|
56
62
|
if not _NAME_OK.match(name):
|
|
57
63
|
raise ValueError(f"charter file name {name!r} must be lowercase [a-z0-9._-]")
|
|
58
64
|
text = path.read_text(encoding="utf-8")
|
|
59
|
-
m = _FRONT_MATTER.match(text)
|
|
60
|
-
if not m:
|
|
61
|
-
raise ValueError(f"{path}: missing front-matter (--- every: 7d ---)")
|
|
62
65
|
meta: dict[str, str] = {}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
m = _COMMENT_META.match(text)
|
|
67
|
+
if m:
|
|
68
|
+
# `<!-- sbxloop: every=7d enabled=false -->` (also `key: value`)
|
|
69
|
+
for token in re.split(r"[\s,;]+", m.group(1).strip()):
|
|
70
|
+
if not token:
|
|
71
|
+
continue
|
|
72
|
+
key, sep, value = token.partition("=") if "=" in token else token.partition(":")
|
|
73
|
+
if sep:
|
|
74
|
+
meta[key.strip().lower()] = value.strip().strip("'\"")
|
|
75
|
+
else:
|
|
76
|
+
m = _FRONT_MATTER.match(text)
|
|
77
|
+
if not m:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"{path}: missing metadata — start the file with "
|
|
80
|
+
"`<!-- sbxloop: every=7d -->` (or a --- every: 7d --- front-matter block)"
|
|
81
|
+
)
|
|
82
|
+
for line in m.group(1).splitlines():
|
|
83
|
+
if ":" in line:
|
|
84
|
+
key, _, value = line.partition(":")
|
|
85
|
+
meta[key.strip().lower()] = value.strip().strip("'\"")
|
|
67
86
|
if "every" not in meta:
|
|
68
87
|
raise ValueError(f"{path}: front-matter needs `every: <interval>`")
|
|
69
88
|
body = text[m.end() :].strip()
|
|
@@ -57,6 +57,9 @@ from sbxloop.sbx.prune import remove_run_sandbox, remove_run_sandbox_secrets
|
|
|
57
57
|
|
|
58
58
|
logger = logging.getLogger(__name__)
|
|
59
59
|
|
|
60
|
+
# How often the audit scheduler fast-forwards the checkout to see new charters.
|
|
61
|
+
AUDIT_REFRESH_S = 600.0
|
|
62
|
+
|
|
60
63
|
|
|
61
64
|
class Frontend(Protocol):
|
|
62
65
|
"""What a human-facing channel (Discord) sees of the loop's lifecycle.
|
|
@@ -127,6 +130,7 @@ class DaemonLoop:
|
|
|
127
130
|
self._stop = threading.Event()
|
|
128
131
|
self._paused = False
|
|
129
132
|
self._audit_problems_seen: set[str] = set()
|
|
133
|
+
self._last_audit_refresh = float("-inf")
|
|
130
134
|
self._current: RunHandle | None = None
|
|
131
135
|
self._current_lock = threading.Lock()
|
|
132
136
|
self._cancel_request: CancelRequest | None = None
|
|
@@ -894,6 +898,13 @@ class DaemonLoop:
|
|
|
894
898
|
checkout = self._workspace_checkout()
|
|
895
899
|
if checkout is None:
|
|
896
900
|
return
|
|
901
|
+
# The checkout is normally refreshed only when a run starts, so
|
|
902
|
+
# charters merged after the last run would never be seen (field:
|
|
903
|
+
# the first deploy read a clone that predated its own charters).
|
|
904
|
+
# Refresh here too, throttled — a fetch a minute is not the point.
|
|
905
|
+
if now - self._last_audit_refresh >= AUDIT_REFRESH_S:
|
|
906
|
+
self._last_audit_refresh = now
|
|
907
|
+
self._refresh_workspace()
|
|
897
908
|
charters, problems = load_charters(checkout, daemon.audit_dir)
|
|
898
909
|
for problem in problems:
|
|
899
910
|
if problem not in self._audit_problems_seen:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|