benchflow 0.7.2.dev1982__py3-none-any.whl → 0.7.2.dev1986__py3-none-any.whl

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.
benchflow/cli/traj.py CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  import os
6
7
  import shutil
7
8
  import subprocess
@@ -29,6 +30,7 @@ from benchflow.publish.traj_capture import (
29
30
  stage_trajectory_artifacts,
30
31
  validate_email,
31
32
  validate_github_id,
33
+ validate_source_id,
32
34
  )
33
35
  from benchflow.publish.traj_report import (
34
36
  DEFAULT_PREVIEW_STEPS,
@@ -127,6 +129,7 @@ class _UploadOptions:
127
129
  github_id: str | None
128
130
  email: str | None
129
131
  source_id: str | None
132
+ repo: bool
130
133
  direct: bool
131
134
  container_url: str | None
132
135
  dry_run: bool
@@ -223,6 +226,14 @@ def register_traj(app: typer.Typer) -> None:
223
226
  str | None,
224
227
  typer.Option("--source-id", help="Stable contributor source identifier"),
225
228
  ] = None,
229
+ repo: Annotated[
230
+ bool,
231
+ typer.Option(
232
+ "--repo/--no-repo",
233
+ help="Tag the upload with the session's repository "
234
+ "(owner/name from its git remote) as the source id",
235
+ ),
236
+ ] = True,
226
237
  direct: Annotated[
227
238
  bool,
228
239
  typer.Option("--direct", help="Upload with local Azure credentials"),
@@ -254,6 +265,7 @@ def register_traj(app: typer.Typer) -> None:
254
265
  github_id=github_id,
255
266
  email=email,
256
267
  source_id=source_id,
268
+ repo=repo,
257
269
  direct=direct,
258
270
  container_url=container_url,
259
271
  dry_run=dry_run,
@@ -268,7 +280,17 @@ def register_traj(app: typer.Typer) -> None:
268
280
  def _run_upload(options: _UploadOptions) -> None:
269
281
  prompted = options.path is None
270
282
  path = options.path or _prompt_for_path()
271
- source_id = options.source_id or default_source_id(path)
283
+ repo_slug: str | None = None
284
+ if options.source_id is not None:
285
+ source_id = options.source_id
286
+ else:
287
+ if options.repo:
288
+ repo_slug = _detect_repo_slug(path)
289
+ source_id = f"repo/{repo_slug}" if repo_slug else default_source_id(path)
290
+ if repo_slug:
291
+ # Contributor-visible metadata: surface the tag so private-repo
292
+ # sessions can opt out before anything leaves the machine.
293
+ console.print(f"Repo: {repo_slug} (use --no-repo to omit)")
272
294
  destination = _resolve_destination(options)
273
295
 
274
296
  with (
@@ -381,6 +403,92 @@ def _infer_email() -> str:
381
403
  return ""
382
404
 
383
405
 
406
+ _SESSION_CWD_SCAN_LINES = 50
407
+
408
+
409
+ def _detect_repo_slug(path: Path) -> str | None:
410
+ """Best-effort ``owner/name`` for the repository the session was about.
411
+
412
+ Reads the working directory the session recorded (Claude events carry a
413
+ ``cwd`` field; Codex ``session_meta`` payloads do too), asks that
414
+ directory's git for the ``origin`` remote, and falls back to the
415
+ invocation directory. Every failure is silent — repo tagging must never
416
+ break an upload — and local-path remotes never produce a tag, so no
417
+ local absolute path can leak into the manifest.
418
+ """
419
+ for candidate in (_session_cwd(path), Path.cwd()):
420
+ if candidate is None or not candidate.is_dir():
421
+ continue
422
+ remote = _command_stdout(
423
+ "git", "-C", str(candidate), "remote", "get-url", "origin"
424
+ )
425
+ slug = _repo_slug_from_remote(remote) if remote else None
426
+ if slug:
427
+ return slug
428
+ return None
429
+
430
+
431
+ def _session_cwd(path: Path) -> Path | None:
432
+ """Working directory recorded by the first session event that has one."""
433
+ session = path.expanduser()
434
+ if not session.is_file():
435
+ return None
436
+ try:
437
+ with session.open(encoding="utf-8", errors="replace") as stream:
438
+ for line_number, line in enumerate(stream):
439
+ if line_number >= _SESSION_CWD_SCAN_LINES:
440
+ break
441
+ body = line.strip()
442
+ if not body:
443
+ continue
444
+ try:
445
+ event = json.loads(body)
446
+ except json.JSONDecodeError:
447
+ continue
448
+ if not isinstance(event, dict):
449
+ continue
450
+ cwd = event.get("cwd")
451
+ if isinstance(cwd, str) and cwd:
452
+ return Path(cwd)
453
+ payload = event.get("payload")
454
+ if (
455
+ event.get("type") == "session_meta"
456
+ and isinstance(payload, dict)
457
+ and isinstance(payload.get("cwd"), str)
458
+ and payload["cwd"]
459
+ ):
460
+ return Path(payload["cwd"])
461
+ except OSError:
462
+ return None
463
+ return None
464
+
465
+
466
+ def _repo_slug_from_remote(remote: str) -> str | None:
467
+ """Normalize an https/ssh git remote URL to ``owner/name``.
468
+
469
+ Only URL-shaped remotes qualify; a filesystem-path remote returns
470
+ ``None`` so local paths never enter the uploaded source id.
471
+ """
472
+ value = remote.strip()
473
+ if "://" in value:
474
+ _, _, rest = value.partition("://")
475
+ _, _, repo_path = rest.partition("/")
476
+ elif ":" in value and "@" in value.partition(":")[0]:
477
+ repo_path = value.partition(":")[2]
478
+ else:
479
+ return None
480
+ repo_path = repo_path.strip("/").removesuffix(".git")
481
+ segments = [segment for segment in repo_path.split("/") if segment]
482
+ if len(segments) < 2:
483
+ return None
484
+ slug = f"{segments[-2]}/{segments[-1]}"
485
+ try:
486
+ validate_source_id(f"repo/{slug}")
487
+ except ValueError:
488
+ return None
489
+ return slug
490
+
491
+
384
492
  def _git_config(key: str) -> str | None:
385
493
  return _command_stdout("git", "config", "--get", key)
386
494
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: benchflow
3
- Version: 0.7.2.dev1982
3
+ Version: 0.7.2.dev1986
4
4
  Summary: Multi-turn agent benchmarking with ACP — run any agent, any model, any provider.
5
5
  Project-URL: Homepage, https://github.com/benchflow-ai/benchflow
6
6
  Project-URL: Repository, https://github.com/benchflow-ai/benchflow
@@ -116,7 +116,7 @@ benchflow/cli/skills.py,sha256=2Apv1iqvmstWEhY6O-PRdxKc8RYwR6f7MvbYfl2dWm0,7497
116
116
  benchflow/cli/tasks.py,sha256=b1XP1EZ79ZtK_1shF65qZyv27EcKKFZYV35M4DzxYKI,18534
117
117
  benchflow/cli/trace_import.py,sha256=NHDieFdiKQ-2Sr1-GF8Z5EDcbGsz2qq_kFE0rXacC2o,14705
118
118
  benchflow/cli/train.py,sha256=MIP_JxptitH348bWWURpcWt-TmDAw1s8d-RUU7jBaIg,23635
119
- benchflow/cli/traj.py,sha256=PYA7Rc04v9nJpltujFDxMBrCanz3Xgp7on8noAv5rA0,19810
119
+ benchflow/cli/traj.py,sha256=2QlXCLNywIKsm8uQVsQ1TAEnaafV6b0dfLx37O2wEsU,23691
120
120
  benchflow/continue_run/__init__.py,sha256=dWidng7kJphVUtbP1a1Ma0Lyjxj5yLgQZIApaKCJUew,1479
121
121
  benchflow/continue_run/batch.py,sha256=8BNSInyp_Ol6Fcrg4ezBJdi3cBS12nC8mf963_x4fIg,4146
122
122
  benchflow/continue_run/orchestrator.py,sha256=wYoseBNE_rZ0EjNwWDyqfave54xNRe43sCd_O4VJjyk,27676
@@ -292,8 +292,8 @@ benchflow/trajectories/tree.py,sha256=c0jyoP9OurDQq-zRtPc66edzWUvLGwQQz0jgtyqSw2
292
292
  benchflow/trajectories/trl_sft_tokenization.py,sha256=-Ef0bP9cuzfzOz5nIv2mEF3taQIEw68h6SM1YO-Mwn4,8643
293
293
  benchflow/trajectories/types.py,sha256=aSJclec3dOCUlfFNDL8GfzvkAcBo5r-dvlHE0MnGdMo,27554
294
294
  benchflow/trajectories/viewer.py,sha256=59wr8pcWWsCYzuI_sFzTSFJFHeXzH6Yklrh8UsF1ksg,26561
295
- benchflow-0.7.2.dev1982.dist-info/METADATA,sha256=JicuzeLyLkPazFxU7P9550DdQK68KOhp54WcuRSr-8I,15261
296
- benchflow-0.7.2.dev1982.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
297
- benchflow-0.7.2.dev1982.dist-info/entry_points.txt,sha256=qPMBVtH4qz3P4nMALEAdTbvpxvsVch9RhqshZKQn1dE,84
298
- benchflow-0.7.2.dev1982.dist-info/licenses/LICENSE,sha256=ohnbHkuBylrTtOvaZJk9Hz7dRwmaEEGAb5ZcBV4Xuhw,10779
299
- benchflow-0.7.2.dev1982.dist-info/RECORD,,
295
+ benchflow-0.7.2.dev1986.dist-info/METADATA,sha256=D7gO4mBmAwaKA5_bFHpuy2Hu2_vfMEBQLdPfVoCyFEk,15261
296
+ benchflow-0.7.2.dev1986.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
297
+ benchflow-0.7.2.dev1986.dist-info/entry_points.txt,sha256=qPMBVtH4qz3P4nMALEAdTbvpxvsVch9RhqshZKQn1dE,84
298
+ benchflow-0.7.2.dev1986.dist-info/licenses/LICENSE,sha256=ohnbHkuBylrTtOvaZJk9Hz7dRwmaEEGAb5ZcBV4Xuhw,10779
299
+ benchflow-0.7.2.dev1986.dist-info/RECORD,,