sbxloop-worker 0.4.0__tar.gz → 0.4.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_worker-0.4.0 → sbxloop_worker-0.4.2}/.gitignore +4 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/PKG-INFO +1 -1
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/pyproject.toml +14 -2
- sbxloop_worker-0.4.2/src/sbxloop_worker/__init__.py +19 -0
- sbxloop_worker-0.4.2/src/sbxloop_worker/_version.py +24 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/backends/copilot.py +26 -8
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/tests/test_runner_unit.py +30 -0
- sbxloop_worker-0.4.0/src/sbxloop_worker/__init__.py +0 -10
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/README.md +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/__main__.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/_json.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/backends/__init__.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/backends/echo.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/events.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/githubops.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/protocol.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/py.typed +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/src/sbxloop_worker/runner.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/tests/conftest.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/tests/test_githubops.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/tests/test_json_extract.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/tests/test_protocol.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/tests/test_worker_contract.py +0 -0
- {sbxloop_worker-0.4.0 → sbxloop_worker-0.4.2}/tests/worker_harness.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sbxloop-worker
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: In-sandbox worker runtime for sbxloop (protocol models, agent backends, job runner)
|
|
5
5
|
Project-URL: Homepage, https://github.com/brettbergin/sbxloop
|
|
6
6
|
Project-URL: Repository, https://github.com/brettbergin/sbxloop
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sbxloop-worker"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"] # derived from git tags by hatch-vcs (see [tool.hatch.version])
|
|
4
4
|
description = "In-sandbox worker runtime for sbxloop (protocol models, agent backends, job runner)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -25,8 +25,20 @@ Homepage = "https://github.com/brettbergin/sbxloop"
|
|
|
25
25
|
Repository = "https://github.com/brettbergin/sbxloop"
|
|
26
26
|
|
|
27
27
|
[build-system]
|
|
28
|
-
requires = ["hatchling>=1.27"]
|
|
28
|
+
requires = ["hatchling>=1.27", "hatch-vcs"]
|
|
29
29
|
build-backend = "hatchling.build"
|
|
30
30
|
|
|
31
|
+
# Both workspace packages version from the same repo-root git tag, keeping the
|
|
32
|
+
# lockstep invariant by construction. `fallback_root` lets a wheel built from
|
|
33
|
+
# an extracted sdist (no git) pick the version up from the sdist's PKG-INFO.
|
|
34
|
+
[tool.hatch.version]
|
|
35
|
+
source = "vcs"
|
|
36
|
+
raw-options = { root = "../..", fallback_root = ".", local_scheme = "no-local-version", fallback_version = "0.0.0" }
|
|
37
|
+
|
|
38
|
+
# Write the resolved version into the package so `sbxloop_worker.__version__`
|
|
39
|
+
# reports it at runtime (inside the sandbox, where dist metadata may be absent).
|
|
40
|
+
[tool.hatch.build.hooks.vcs]
|
|
41
|
+
version-file = "src/sbxloop_worker/_version.py"
|
|
42
|
+
|
|
31
43
|
[tool.hatch.build.targets.wheel]
|
|
32
44
|
packages = ["src/sbxloop_worker"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""sbxloop-worker — the in-sandbox runtime for sbxloop.
|
|
2
|
+
|
|
3
|
+
Runs inside a Docker Sandbox microVM. Hosts the shared host/worker protocol
|
|
4
|
+
models, the agent backends (GitHub Copilot SDK), and the job runner invoked
|
|
5
|
+
via ``python -m sbxloop_worker``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
# written at build time by hatch-vcs (see pyproject [tool.hatch.build.hooks.vcs])
|
|
10
|
+
from sbxloop_worker._version import __version__
|
|
11
|
+
except ImportError: # pragma: no cover - raw source tree that was never built
|
|
12
|
+
try:
|
|
13
|
+
from importlib.metadata import version as _pkg_version
|
|
14
|
+
|
|
15
|
+
__version__ = _pkg_version("sbxloop-worker")
|
|
16
|
+
except Exception:
|
|
17
|
+
__version__ = "0.0.0"
|
|
18
|
+
|
|
19
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.4.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 4, 2)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -69,7 +69,7 @@ def _tool_args(arguments: Any) -> str | None:
|
|
|
69
69
|
if arguments is None:
|
|
70
70
|
return None
|
|
71
71
|
if isinstance(arguments, dict):
|
|
72
|
-
for key in ("command", "cmd", "input", "query", "path"):
|
|
72
|
+
for key in ("command", "cmd", "input", "query", "pattern", "path"):
|
|
73
73
|
value = arguments.get(key)
|
|
74
74
|
if isinstance(value, str) and value.strip():
|
|
75
75
|
return value[:TOOL_ARGS_CLIP]
|
|
@@ -106,6 +106,20 @@ def _tool_output(data: Any) -> str | None:
|
|
|
106
106
|
return content[-TOOL_OUTPUT_CLIP:]
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def _tool_error(data: Any) -> str | None:
|
|
110
|
+
"""The failure reason from a ToolExecutionComplete, when present.
|
|
111
|
+
|
|
112
|
+
On failed executions the SDK leaves ``result`` unset (it is documented
|
|
113
|
+
as "tool execution result on success") and reports the reason in
|
|
114
|
+
``error.message`` instead, so this is the only failure text available.
|
|
115
|
+
"""
|
|
116
|
+
error = getattr(data, "error", None)
|
|
117
|
+
message = getattr(error, "message", None)
|
|
118
|
+
if not isinstance(message, str) or not message.strip():
|
|
119
|
+
return None
|
|
120
|
+
return message[-TOOL_OUTPUT_CLIP:]
|
|
121
|
+
|
|
122
|
+
|
|
109
123
|
class CopilotBackend:
|
|
110
124
|
name = "copilot"
|
|
111
125
|
|
|
@@ -129,9 +143,9 @@ class CopilotBackend:
|
|
|
129
143
|
|
|
130
144
|
usage = Usage()
|
|
131
145
|
final_text: list[str] = []
|
|
132
|
-
# tool_call_id -> tool name, so completion events
|
|
133
|
-
# (the SDK's Complete event carries only the call id).
|
|
134
|
-
|
|
146
|
+
# tool_call_id -> (tool name, displayed args), so completion events
|
|
147
|
+
# can say what ran (the SDK's Complete event carries only the call id).
|
|
148
|
+
tool_calls: dict[str, tuple[str | None, str | None]] = {}
|
|
135
149
|
|
|
136
150
|
def on_event(event: Any) -> None:
|
|
137
151
|
nonlocal usage
|
|
@@ -150,23 +164,27 @@ class CopilotBackend:
|
|
|
150
164
|
elif type_name.startswith("ToolExecutionStart"):
|
|
151
165
|
tool = getattr(data, "tool_name", None) or getattr(data, "toolName", None)
|
|
152
166
|
call_id = getattr(data, "tool_call_id", None) or getattr(data, "toolCallId", None)
|
|
153
|
-
|
|
154
|
-
|
|
167
|
+
args = _tool_args(getattr(data, "arguments", None))
|
|
168
|
+
if call_id:
|
|
169
|
+
tool_calls[str(call_id)] = (str(tool) if tool else None, args)
|
|
155
170
|
emit(
|
|
156
171
|
EventTypes.AGENT_TOOL_START,
|
|
157
172
|
tool=tool,
|
|
158
173
|
tool_call_id=call_id,
|
|
159
|
-
args=
|
|
174
|
+
args=args,
|
|
160
175
|
)
|
|
161
176
|
elif type_name.startswith("ToolExecutionComplete"):
|
|
162
177
|
call_id = getattr(data, "tool_call_id", None) or getattr(data, "toolCallId", None)
|
|
178
|
+
tool, args = tool_calls.get(str(call_id), (None, None))
|
|
163
179
|
emit(
|
|
164
180
|
EventTypes.AGENT_TOOL_END,
|
|
165
181
|
tool_call_id=call_id,
|
|
166
|
-
tool=
|
|
182
|
+
tool=tool,
|
|
183
|
+
args=args,
|
|
167
184
|
success=getattr(data, "success", None),
|
|
168
185
|
exit_code=_tool_exit_code(data),
|
|
169
186
|
output=_tool_output(data),
|
|
187
|
+
error=_tool_error(data),
|
|
170
188
|
)
|
|
171
189
|
elif type_name == "AssistantUsageData":
|
|
172
190
|
sample = Usage(
|
|
@@ -109,6 +109,36 @@ class TestBackendRegistry:
|
|
|
109
109
|
backend.run_session(agent_job(), lambda *a, **k: None) # type: ignore[arg-type,return-value]
|
|
110
110
|
|
|
111
111
|
|
|
112
|
+
class TestCopilotEventHelpers:
|
|
113
|
+
"""Pure helpers from the copilot backend (importable without the SDK)."""
|
|
114
|
+
|
|
115
|
+
def test_tool_args_prefers_glob_pattern(self) -> None:
|
|
116
|
+
from sbxloop_worker.backends.copilot import _tool_args
|
|
117
|
+
|
|
118
|
+
assert _tool_args({"pattern": "**/*.py"}) == "**/*.py"
|
|
119
|
+
|
|
120
|
+
def test_tool_error_reads_failure_message(self) -> None:
|
|
121
|
+
from types import SimpleNamespace
|
|
122
|
+
|
|
123
|
+
from sbxloop_worker.backends.copilot import _tool_error
|
|
124
|
+
|
|
125
|
+
# A failed ToolExecutionComplete: result is None, error carries the
|
|
126
|
+
# reason (the SDK documents `result` as success-only).
|
|
127
|
+
data = SimpleNamespace(
|
|
128
|
+
success=False,
|
|
129
|
+
result=None,
|
|
130
|
+
error=SimpleNamespace(message="permission denied", code=None),
|
|
131
|
+
)
|
|
132
|
+
assert _tool_error(data) == "permission denied"
|
|
133
|
+
|
|
134
|
+
def test_tool_error_none_when_absent(self) -> None:
|
|
135
|
+
from types import SimpleNamespace
|
|
136
|
+
|
|
137
|
+
from sbxloop_worker.backends.copilot import _tool_error
|
|
138
|
+
|
|
139
|
+
assert _tool_error(SimpleNamespace(success=True, result=None, error=None)) is None
|
|
140
|
+
|
|
141
|
+
|
|
112
142
|
class TestEchoScriptEdgeCases:
|
|
113
143
|
def test_script_entry_must_be_object(
|
|
114
144
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"""sbxloop-worker — the in-sandbox runtime for sbxloop.
|
|
2
|
-
|
|
3
|
-
Runs inside a Docker Sandbox microVM. Hosts the shared host/worker protocol
|
|
4
|
-
models, the agent backends (GitHub Copilot SDK), and the job runner invoked
|
|
5
|
-
via ``python -m sbxloop_worker``.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
__version__ = "0.4.0"
|
|
9
|
-
|
|
10
|
-
__all__ = ["__version__"]
|
|
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
|