sbxloop 0.5.1__tar.gz → 0.5.2__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.5.1 → sbxloop-0.5.2}/PKG-INFO +1 -1
- sbxloop-0.5.1/src/sbxloop/_vendor/sbxloop_worker-0.5.1-py3-none-any.whl → sbxloop-0.5.2/src/sbxloop/_vendor/sbxloop_worker-0.5.2-py3-none-any.whl +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/_version.py +2 -2
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/engine.py +20 -4
- sbxloop-0.5.2/src/sbxloop/gh/reporter.py +99 -0
- sbxloop-0.5.1/src/sbxloop/gh/reporter.py +0 -76
- {sbxloop-0.5.1 → sbxloop-0.5.2}/.gitignore +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/README.md +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/hatch_build.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/pyproject.toml +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/__init__.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/cli/__init__.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/cli/app.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/cli/doctor.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/cli/tui.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/config.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/deliver.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/__init__.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/model.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/phases.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/prompts/__init__.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/prompts/decompose.md +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/prompts/execute.md +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/prompts/plan.md +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/prompts/scrutinize.md +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/prompts/validate.md +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/engine/store.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/errors.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/events.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/gh/__init__.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/gh/ops.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/ids.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/py.typed +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/sbx/__init__.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/sbx/cli.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/sbx/models.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/sbx/pair.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/sbx/parse.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/sbx/provision.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/sbx/sandbox.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/worker/__init__.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/worker/client.py +0 -0
- {sbxloop-0.5.1 → sbxloop-0.5.2}/src/sbxloop/worker/wheel.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sbxloop
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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.5.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 5,
|
|
21
|
+
__version__ = version = '0.5.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 5, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -139,12 +139,17 @@ class LoopEngine:
|
|
|
139
139
|
agent.install(extras="copilot", ensure_dev_tools=True)
|
|
140
140
|
if github is not None:
|
|
141
141
|
github.install(extras="")
|
|
142
|
-
detach = self._attach_reporter(github, run_id)
|
|
142
|
+
reporter, detach = self._attach_reporter(github, run_id, outcome)
|
|
143
143
|
try:
|
|
144
144
|
phases = PhaseRunner(
|
|
145
145
|
agent, self.config, run_id, outcome, workdir=pair.agent_workdir
|
|
146
146
|
)
|
|
147
147
|
state = self._run_phases(run_id, phases, deadline, pair)
|
|
148
|
+
# Summary must post while the github sandbox is alive;
|
|
149
|
+
# on an infra exception the run is resumable and the
|
|
150
|
+
# resumed run reopens the same tracking issue.
|
|
151
|
+
if reporter is not None:
|
|
152
|
+
reporter.close_run(run_id, state)
|
|
148
153
|
finally:
|
|
149
154
|
detach()
|
|
150
155
|
# Harvest even when a phase raised: the sandbox is still
|
|
@@ -167,13 +172,24 @@ class LoopEngine:
|
|
|
167
172
|
mounted=pair.mounted,
|
|
168
173
|
)
|
|
169
174
|
|
|
170
|
-
def _attach_reporter(
|
|
175
|
+
def _attach_reporter(
|
|
176
|
+
self, github: WorkerClient | None, run_id: str, outcome: str
|
|
177
|
+
) -> tuple[GithubReporterHook | None, Callable[[], None]]:
|
|
178
|
+
"""Attach progress reporting; opens the tracking issue immediately.
|
|
179
|
+
|
|
180
|
+
Run start/end go through explicit ``open_run``/``close_run`` calls
|
|
181
|
+
rather than bus events: RUN_START is emitted before the github
|
|
182
|
+
sandbox exists and RUN_END after it is gone, so the hook could never
|
|
183
|
+
observe them (#58).
|
|
184
|
+
"""
|
|
171
185
|
gh = self.config.github
|
|
172
186
|
if not gh.report or github is None:
|
|
173
|
-
return lambda: None
|
|
187
|
+
return None, lambda: None
|
|
174
188
|
assert gh.repo is not None # report=True without a repo cannot provision a github worker
|
|
175
189
|
hook = GithubReporterHook(GithubOps(github, run_id), gh.repo)
|
|
176
|
-
|
|
190
|
+
detach = self.bus.attach_hook(hook)
|
|
191
|
+
hook.open_run(run_id, outcome)
|
|
192
|
+
return hook, detach
|
|
177
193
|
|
|
178
194
|
def _harvest(self, run_id: str, pair: SandboxPair) -> None:
|
|
179
195
|
"""Copy the in-VM work dir out to the host (unmounted runs only).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""GithubReporterHook: mirrors run progress to a GitHub tracking issue.
|
|
2
|
+
|
|
3
|
+
The default consumer of the github-ops sandbox. When the GitHub integration
|
|
4
|
+
is configured (``[github].repo``) and reporting is enabled (``report = true``
|
|
5
|
+
or ``--report``), the engine opens a tracking issue right after the github
|
|
6
|
+
sandbox is ready, comments as tasks finish, and posts a final summary while
|
|
7
|
+
the sandbox is still alive.
|
|
8
|
+
|
|
9
|
+
Run start/end are **explicit calls** (``open_run``/``close_run``) rather
|
|
10
|
+
than bus events: the run lifecycle events are emitted outside the window in
|
|
11
|
+
which the hook is attached and the github sandbox exists, so subscribing to
|
|
12
|
+
them can never work — see #58. Only per-task progress arrives via the bus.
|
|
13
|
+
|
|
14
|
+
Every entry point is guarded: a reporting failure must not fail a run — and
|
|
15
|
+
the EventBus isolates ``on_event`` anyway.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
|
|
22
|
+
from sbxloop.events import Event, HostEventTypes
|
|
23
|
+
from sbxloop.gh.ops import GithubOps, IssueRef
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GithubReporterHook:
|
|
29
|
+
def __init__(self, ops: GithubOps, repo: str) -> None:
|
|
30
|
+
self.ops = ops
|
|
31
|
+
self.repo = repo
|
|
32
|
+
self.issue: IssueRef | None = None
|
|
33
|
+
self._task_lines: list[str] = []
|
|
34
|
+
|
|
35
|
+
# -- explicit lifecycle (called by the engine, sandbox guaranteed alive) --
|
|
36
|
+
|
|
37
|
+
def open_run(self, run_id: str, outcome: str) -> None:
|
|
38
|
+
"""Open (or on resume, re-find) the run's tracking issue."""
|
|
39
|
+
try:
|
|
40
|
+
self.issue = self._find_existing(run_id) or self._create(run_id, outcome)
|
|
41
|
+
except Exception:
|
|
42
|
+
logger.warning("github reporting: opening tracking issue failed", exc_info=True)
|
|
43
|
+
|
|
44
|
+
def close_run(self, run_id: str, state: str) -> None:
|
|
45
|
+
"""Post the final summary comment; call before sandbox teardown."""
|
|
46
|
+
if self.issue is None:
|
|
47
|
+
return
|
|
48
|
+
summary = "\n".join(self._task_lines) or "_no tasks were executed_"
|
|
49
|
+
try:
|
|
50
|
+
self.ops.issue_comment(
|
|
51
|
+
self.repo,
|
|
52
|
+
self.issue.number,
|
|
53
|
+
f"Run `{run_id}` finished: **{state}**\n\n{summary}",
|
|
54
|
+
)
|
|
55
|
+
except Exception:
|
|
56
|
+
logger.warning("github reporting: final summary failed", exc_info=True)
|
|
57
|
+
|
|
58
|
+
# -- Hook protocol (task progress only) ----------------------------------
|
|
59
|
+
|
|
60
|
+
def on_event(self, event: Event) -> None:
|
|
61
|
+
try:
|
|
62
|
+
if event.type == HostEventTypes.TASK_END:
|
|
63
|
+
self._on_task_end(event)
|
|
64
|
+
except Exception:
|
|
65
|
+
logger.warning("github reporting failed for %s", event.type, exc_info=True)
|
|
66
|
+
|
|
67
|
+
# -- internals -----------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
def _find_existing(self, run_id: str) -> IssueRef | None:
|
|
70
|
+
"""A resumed run reuses its issue instead of opening a duplicate."""
|
|
71
|
+
query = f'repo:{self.repo} is:issue in:title "sbxloop run {run_id}"'
|
|
72
|
+
for item in self.ops.search_issues(query, per_page=5):
|
|
73
|
+
if item.get("title") == f"sbxloop run {run_id}" and item.get("number"):
|
|
74
|
+
return IssueRef(number=int(item["number"]), url=str(item.get("html_url", "")))
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def _create(self, run_id: str, outcome: str) -> IssueRef:
|
|
78
|
+
body = (
|
|
79
|
+
f"sbxloop run `{run_id}` started.\n\n"
|
|
80
|
+
f"**Outcome:**\n\n> {outcome}\n\n"
|
|
81
|
+
"Progress is reported as comments on this issue."
|
|
82
|
+
)
|
|
83
|
+
return self.ops.issue_create(
|
|
84
|
+
self.repo,
|
|
85
|
+
title=f"sbxloop run {run_id}",
|
|
86
|
+
body=body,
|
|
87
|
+
labels=["sbxloop"],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def _on_task_end(self, event: Event) -> None:
|
|
91
|
+
if self.issue is None:
|
|
92
|
+
return
|
|
93
|
+
task_id = event.data.get("task_id", "?")
|
|
94
|
+
title = event.data.get("title", "")
|
|
95
|
+
state = event.data.get("state", "?")
|
|
96
|
+
marker = "✅" if state == "done" else "❌"
|
|
97
|
+
line = f"{marker} `{task_id}` {title} — **{state}**"
|
|
98
|
+
self._task_lines.append(line)
|
|
99
|
+
self.ops.issue_comment(self.repo, self.issue.number, line)
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"""GithubReporterHook: mirrors run progress to a GitHub tracking issue.
|
|
2
|
-
|
|
3
|
-
The default consumer of the github-ops sandbox. When the GitHub integration
|
|
4
|
-
is configured (``[github].repo``) and reporting is enabled (``report = true``
|
|
5
|
-
or ``--report``), the hook opens a tracking issue at run start, comments as
|
|
6
|
-
tasks finish, and posts a final summary at run end. It never raises — a
|
|
7
|
-
reporting failure must not fail a run — and the EventBus isolates it anyway.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import logging
|
|
13
|
-
|
|
14
|
-
from sbxloop.events import Event, HostEventTypes
|
|
15
|
-
from sbxloop.gh.ops import GithubOps, IssueRef
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class GithubReporterHook:
|
|
21
|
-
def __init__(self, ops: GithubOps, repo: str) -> None:
|
|
22
|
-
self.ops = ops
|
|
23
|
-
self.repo = repo
|
|
24
|
-
self.issue: IssueRef | None = None
|
|
25
|
-
self._task_lines: list[str] = []
|
|
26
|
-
|
|
27
|
-
# -- Hook protocol -----------------------------------------------------
|
|
28
|
-
|
|
29
|
-
def on_event(self, event: Event) -> None:
|
|
30
|
-
try:
|
|
31
|
-
if event.type == HostEventTypes.RUN_START:
|
|
32
|
-
self._on_run_start(event)
|
|
33
|
-
elif event.type == HostEventTypes.TASK_END:
|
|
34
|
-
self._on_task_end(event)
|
|
35
|
-
elif event.type == HostEventTypes.RUN_END:
|
|
36
|
-
self._on_run_end(event)
|
|
37
|
-
except Exception:
|
|
38
|
-
logger.warning("github reporting failed for %s", event.type, exc_info=True)
|
|
39
|
-
|
|
40
|
-
# -- handlers ----------------------------------------------------------
|
|
41
|
-
|
|
42
|
-
def _on_run_start(self, event: Event) -> None:
|
|
43
|
-
outcome = str(event.data.get("outcome", ""))
|
|
44
|
-
body = (
|
|
45
|
-
f"sbxloop run `{event.run_id}` started.\n\n"
|
|
46
|
-
f"**Outcome:**\n\n> {outcome}\n\n"
|
|
47
|
-
"Progress is reported as comments on this issue."
|
|
48
|
-
)
|
|
49
|
-
self.issue = self.ops.issue_create(
|
|
50
|
-
self.repo,
|
|
51
|
-
title=f"sbxloop run {event.run_id}",
|
|
52
|
-
body=body,
|
|
53
|
-
labels=["sbxloop"],
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
def _on_task_end(self, event: Event) -> None:
|
|
57
|
-
if self.issue is None:
|
|
58
|
-
return
|
|
59
|
-
task_id = event.data.get("task_id", "?")
|
|
60
|
-
title = event.data.get("title", "")
|
|
61
|
-
state = event.data.get("state", "?")
|
|
62
|
-
marker = "✅" if state == "done" else "❌"
|
|
63
|
-
line = f"{marker} `{task_id}` {title} — **{state}**"
|
|
64
|
-
self._task_lines.append(line)
|
|
65
|
-
self.ops.issue_comment(self.repo, self.issue.number, line)
|
|
66
|
-
|
|
67
|
-
def _on_run_end(self, event: Event) -> None:
|
|
68
|
-
if self.issue is None:
|
|
69
|
-
return
|
|
70
|
-
state = event.data.get("state", "?")
|
|
71
|
-
summary = "\n".join(self._task_lines) or "_no tasks were executed_"
|
|
72
|
-
self.ops.issue_comment(
|
|
73
|
-
self.repo,
|
|
74
|
-
self.issue.number,
|
|
75
|
-
f"Run `{event.run_id}` finished: **{state}**\n\n{summary}",
|
|
76
|
-
)
|
|
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
|