From 89d48465901f642c406479517199b84aa38de879 Mon Sep 17 00:00:00 2001 From: Pepijn Date: Mon, 18 May 2026 14:04:53 +0200 Subject: [PATCH] fix(smolvla2): always show the startup task picker on a TTY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The picker was skipped whenever a task was already resolved — which is always the case with --dataset.repo_id, since the dataset's canonical task is auto-filled. The operator never got to choose. Now the picker always runs on an interactive terminal: the resolved task is shown as "(current)" and selected by an empty Enter, so the dataset-canonical default still works while letting the operator pick another task or type a custom one. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../scripts/lerobot_smolvla2_runtime.py | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/lerobot/scripts/lerobot_smolvla2_runtime.py b/src/lerobot/scripts/lerobot_smolvla2_runtime.py index 3182386c5..635982ad9 100644 --- a/src/lerobot/scripts/lerobot_smolvla2_runtime.py +++ b/src/lerobot/scripts/lerobot_smolvla2_runtime.py @@ -918,33 +918,38 @@ def _dataset_task_strings(ds_meta: Any) -> list[str]: def _select_task_interactively(ds_meta: Any, current_task: str | None) -> str | None: """Prompt the operator to pick a task from the dataset or type one. - Called at startup when no ``--task`` was given. Non-TTY / scripted - runs return ``current_task`` unchanged so the existing - "first stdin line becomes the task" behaviour is preserved. + Called at startup. ``current_task`` is whatever was already resolved + (``--task`` or the dataset's canonical task); it becomes the default + that an empty ``Enter`` selects, and is marked ``(current)`` in the + menu. Non-TTY / scripted runs return ``current_task`` unchanged so + the existing "first stdin line becomes the task" behaviour is kept. """ - if current_task: - return current_task if not sys.stdin.isatty(): return current_task tasks = _dataset_task_strings(ds_meta) if not tasks: + prompt = "[smolvla2] Enter the task" + if current_task: + prompt += f" [Enter = {current_task!r}]" try: - typed = input("[smolvla2] Enter the task: ").strip() + typed = input(prompt + ": ").strip() except (EOFError, KeyboardInterrupt): return current_task return typed or current_task print("[smolvla2] Select a task:", flush=True) for i, task in enumerate(tasks, 1): - print(f" [{i}] {task}", flush=True) + marker = " (current)" if task == current_task else "" + print(f" [{i}] {task}{marker}", flush=True) print(" [c] type a custom task", flush=True) + hint = " (Enter = current)" if current_task else "" try: - raw = input("task> ").strip() + raw = input(f"task>{hint} ").strip() except (EOFError, KeyboardInterrupt): return current_task if not raw: - return tasks[0] + return current_task or tasks[0] if raw.lower() in {"c", "custom"}: try: return input("[smolvla2] Enter the task: ").strip() or current_task @@ -954,8 +959,8 @@ def _select_task_interactively(ds_meta: Any, current_task: str | None) -> str | idx = int(raw) - 1 if 0 <= idx < len(tasks): return tasks[idx] - print("[smolvla2] invalid choice — using the first task", flush=True) - return tasks[0] + print("[smolvla2] invalid choice — keeping the current task", flush=True) + return current_task or tasks[0] # Treat anything else as a custom task string typed directly. return raw @@ -1398,11 +1403,11 @@ def main(argv: list[str] | None = None) -> int: flush=True, ) - # No task yet (no --task, no canonical dataset task) — let the - # operator pick one from the dataset's task list or type a custom - # one. Non-TTY runs keep the "first stdin line is the task" path. - if not args.task: - args.task = _select_task_interactively(ds_meta, args.task) + # Always offer the startup task picker on an interactive terminal: + # list the dataset's tasks (the canonical / --task one shown as the + # default) so the operator can pick another or type a custom task. + # Non-TTY runs keep the "first stdin line is the task" path. + args.task = _select_task_interactively(ds_meta, args.task) observation_provider: Callable[[], dict | None] | None = None robot_executor: Callable[[Any], None] | None = None