fix(dashboard): hoist claude_argv to Bottle ABC so smolmachines pane attach works
Launching a smolmachines agent from the dashboard inside tmux crashed with AttributeError: 'SmolmachinesBottle' object has no attribute 'claude_docker_argv' because the tmux pane-respawn path called `bottle.claude_docker_argv(...)` directly — a method that only existed on DockerBottle. The foreground-handoff path (curses endwin → subprocess.run → restore) doesn't hit it; it goes through `bottle.exec_claude` which is on the ABC. - Move the argv builder onto the `Bottle` ABC as `claude_argv(argv, *, tty=True) -> list[str]`. Both backends implement it; both `exec_claude` impls collapse to `subprocess.run(self.claude_argv(argv, tty=tty), check=False)`. - DockerBottle: rename `claude_docker_argv` → `claude_argv`, body unchanged. - SmolmachinesBottle: extract the argv-building from `exec_claude` into `claude_argv`; the new method returns the full `smolvm machine exec --name … -- runuser -u node -- claude …` argv. The `runuser` switch lives on the exec-framing prefix so the dashboard's `_build_resume_argv_with_fallback` split-at-"claude" trick keeps the UID switch when wrapping the claude tail in `sh -c "… --continue || …"`. - Dashboard: drop the docker-specific wording — local + helper arg names `docker_argv` → `claude_argv`; docstrings on `_build_resume_argv_with_fallback`, `_build_split_pane_argv`, `_build_respawn_pane_argv` now say "backend-exec argv". The shell-fallback wrap is unchanged; the existing logic works for smolmachines because `claude` is still the marker token. Tests: - `tests/unit/test_smolmachines_bottle.py` (new): locks down the smolmachines argv shape — prompt-file flag injection, guest-env `-e K=V` forwarding, TTY toggle, runuser-precedes- claude invariant. - `test_docker_bottle.py`: TestClaudeDockerArgv → TestClaudeArgv; method renames follow. - `test_dashboard_active_agents.py`: docstring follow. 615 unit tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit was merged in pull request #81.
This commit is contained in:
@@ -75,6 +75,21 @@ class SmolmachinesBottle(Bottle):
|
||||
# because exec doesn't inherit from machine_create's env.
|
||||
self._guest_env = dict(guest_env or {})
|
||||
|
||||
def claude_argv(
|
||||
self, argv: list[str], *, tty: bool = True,
|
||||
) -> list[str]:
|
||||
flags = ["smolvm", "machine", "exec", "--name", self.name]
|
||||
if tty:
|
||||
flags += ["-i", "-t"]
|
||||
flags += _env_flags_for("node")
|
||||
flags += _guest_env_flags(self._guest_env)
|
||||
claude_tail = ["claude"]
|
||||
if self._prompt_path:
|
||||
claude_tail += ["--append-system-prompt-file", self._prompt_path]
|
||||
claude_tail += argv
|
||||
flags += ["--", "runuser", "-u", "node", "--", *claude_tail]
|
||||
return flags
|
||||
|
||||
def exec_claude(self, argv: list[str], *, tty: bool = True) -> int:
|
||||
"""Run `claude` interactively inside the VM as the `node`
|
||||
user. Inherits the operator's terminal (stdin / stdout /
|
||||
@@ -89,18 +104,9 @@ class SmolmachinesBottle(Bottle):
|
||||
UID switches via `runuser -u node --` (not `-l`) so we
|
||||
avoid login-shell wiring. HOME / USER come from `smolvm
|
||||
-e` instead, which sets them on the process env."""
|
||||
flags = ["smolvm", "machine", "exec", "--name", self.name]
|
||||
if tty:
|
||||
flags += ["-i", "-t"]
|
||||
flags += _env_flags_for("node")
|
||||
flags += _guest_env_flags(self._guest_env)
|
||||
claude_argv = ["claude"]
|
||||
if self._prompt_path:
|
||||
claude_argv += ["--append-system-prompt-file", self._prompt_path]
|
||||
claude_argv += argv
|
||||
flags += ["--", "runuser", "-u", "node", "--", *claude_argv]
|
||||
result = subprocess.run(flags, check=False)
|
||||
return result.returncode
|
||||
return subprocess.run(
|
||||
self.claude_argv(argv, tty=tty), check=False,
|
||||
).returncode
|
||||
|
||||
def exec(self, script: str, *, user: str = "node") -> ExecResult:
|
||||
"""Run a POSIX shell script as `user` (default `node`) and
|
||||
|
||||
Reference in New Issue
Block a user