sbxloop-worker 0.2.0__tar.gz → 0.3.0__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.
Files changed (22) hide show
  1. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/PKG-INFO +1 -1
  2. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/pyproject.toml +1 -1
  3. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/__init__.py +1 -1
  4. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/__main__.py +13 -0
  5. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/backends/copilot.py +15 -0
  6. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/backends/echo.py +9 -1
  7. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/protocol.py +4 -0
  8. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/.gitignore +0 -0
  9. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/README.md +0 -0
  10. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/_json.py +0 -0
  11. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/backends/__init__.py +0 -0
  12. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/events.py +0 -0
  13. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/githubops.py +0 -0
  14. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/py.typed +0 -0
  15. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/src/sbxloop_worker/runner.py +0 -0
  16. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/tests/conftest.py +0 -0
  17. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/tests/test_githubops.py +0 -0
  18. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/tests/test_json_extract.py +0 -0
  19. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/tests/test_protocol.py +0 -0
  20. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/tests/test_runner_unit.py +0 -0
  21. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/tests/test_worker_contract.py +0 -0
  22. {sbxloop_worker-0.2.0 → sbxloop_worker-0.3.0}/tests/worker_harness.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sbxloop-worker
3
- Version: 0.2.0
3
+ Version: 0.3.0
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
- version = "0.2.0"
3
+ version = "0.3.0"
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"
@@ -5,6 +5,6 @@ models, the agent backends (GitHub Copilot SDK), and the job runner invoked
5
5
  via ``python -m sbxloop_worker``.
6
6
  """
7
7
 
8
- __version__ = "0.2.0"
8
+ __version__ = "0.3.0"
9
9
 
10
10
  __all__ = ["__version__"]
@@ -65,6 +65,7 @@ def main(argv: list[str] | None = None) -> int:
65
65
  run.add_argument("--result", required=True, type=Path)
66
66
  run.add_argument("--heartbeat", type=float, default=15.0)
67
67
  run.add_argument("--env-file", type=Path, default=DEFAULT_ENV_FILE)
68
+ run.add_argument("--cwd", type=Path, default=None)
68
69
  args = parser.parse_args(argv)
69
70
 
70
71
  apply_env_file(args.env_file)
@@ -76,6 +77,18 @@ def main(argv: list[str] | None = None) -> int:
76
77
  print(f"sbxloop_worker: invalid job file {args.job}: {exc}", file=sys.stderr)
77
78
  return 64
78
79
 
80
+ if args.cwd is not None:
81
+ # Run the whole worker in the job's working directory so agent
82
+ # sessions (which inherit the process cwd) and shell commands both
83
+ # execute in the run workspace. Overwrite job.cwd with the resolved
84
+ # value so shell.check keeps its single code path.
85
+ try:
86
+ os.chdir(args.cwd)
87
+ except OSError as exc:
88
+ print(f"sbxloop_worker: cannot chdir to {args.cwd}: {exc}", file=sys.stderr)
89
+ return 64
90
+ job = job.model_copy(update={"cwd": str(Path.cwd())})
91
+
79
92
  try:
80
93
  JobRunner(
81
94
  job,
@@ -142,6 +142,21 @@ class CopilotBackend:
142
142
  kwargs["model"] = job.model
143
143
  if job.system_message:
144
144
  kwargs["system_message"] = {"mode": "append", "content": job.system_message}
145
+ if job.cwd:
146
+ # Belt-and-braces alongside the worker-level chdir; the kwarg
147
+ # name is unverified against the SDK (e2e-validated), so an
148
+ # unsupported signature must not fail the session.
149
+ kwargs["working_directory"] = job.cwd
150
+ try:
151
+ return await self._open(client, job, kwargs)
152
+ except TypeError:
153
+ if "working_directory" not in kwargs:
154
+ raise
155
+ del kwargs["working_directory"]
156
+ return await self._open(client, job, kwargs)
157
+
158
+ @staticmethod
159
+ async def _open(client: Any, job: JobRequest, kwargs: dict[str, Any]) -> Any:
145
160
  if job.resume_session_id:
146
161
  return await client.resume_session(job.resume_session_id, **kwargs)
147
162
  return await client.create_session(**kwargs)
@@ -7,7 +7,11 @@ multi-job engine tests can script an entire run.
7
7
 
8
8
  Response object shape (all fields optional):
9
9
  ``{"text": str, "json": dict|list, "session_id": str, "sleep_s": float,
10
- "events": [{"type": str, "data": {...}}], "fail": str}``
10
+ "events": [{"type": str, "data": {...}}], "fail": str,
11
+ "files": {"relative/path": "content"}}``
12
+
13
+ ``files`` are written relative to the worker process cwd — modelling an
14
+ executor that produces artifacts in the run workspace.
11
15
  """
12
16
 
13
17
  from __future__ import annotations
@@ -73,6 +77,10 @@ class EchoBackend:
73
77
  raise RuntimeError(str(response["fail"]))
74
78
  if sleep_s := float(response.get("sleep_s", 0)):
75
79
  time.sleep(sleep_s)
80
+ for relative, content in response.get("files", {}).items():
81
+ target = Path.cwd() / relative
82
+ target.parent.mkdir(parents=True, exist_ok=True)
83
+ target.write_text(str(content))
76
84
  for scripted_event in response.get("events", []):
77
85
  emit(scripted_event["type"], **scripted_event.get("data", {}))
78
86
  text = str(response.get("text", ""))
@@ -92,6 +92,10 @@ class JobRequest(ProtocolModel):
92
92
 
93
93
  # kind == "shell.check"
94
94
  argv: list[str] | None = None
95
+
96
+ # agent.session + shell.check: in-sandbox working directory. The worker
97
+ # process chdirs here (via --cwd) so agent sessions and shell commands
98
+ # run in the run's canonical workspace.
95
99
  cwd: str | None = None
96
100
 
97
101
  # kind == "github.op"
File without changes