feat(agent-provider): user plugin discovery, Dockerfile cascade, and provider-owned ca/git provisioning
- Add _load_user_plugin: loads AgentProvider subclass from ~/.bot-bottle/contrib/<name>/agent_provider.py; get_provider() checks there first before falling back to built-ins - Add Dockerfile cascade to docker prepare: per-bottle override → manifest dockerfile → user plugin Dockerfile → provider default - Move provision_ca and provision_git from backend-specific provision/ modules to AgentProvider ABC as overridable defaults; delete docker/provision/ca.py, docker/provision/git.py, smolmachines/provision/ca.py, smolmachines/provision/git.py - Add git_gate_insteadof_host/scheme properties to BottlePlan base; SmolmachinesBottlePlan overrides them to return agent_git_gate_host and "http" so provision_git works correctly on both backends - Move SIGKILL retry from smolmachines provision/ca.py into SmolmachinesBottle.exec via _exec_raw helper — all exec calls on smolmachines now transparently retry once on exit 137 - Relax manifest_agent template validation to allow user-defined template names; keep auth_token/forward_host_credentials guards for built-in-only features - Update tests: rewrite test_docker_provision_git_user and test_smolmachines_provision to call provider methods directly; add TestSmolmachinesBottleExec for SIGKILL retry coverage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Mapping, cast
|
||||
|
||||
from ...agent_provider import PromptMode, prompt_args
|
||||
@@ -131,6 +132,11 @@ class SmolmachinesBottle(Bottle):
|
||||
self.agent_argv(argv, tty=tty), check=False,
|
||||
).returncode
|
||||
|
||||
# smolvm/libkrun can SIGKILL an otherwise-normal exec during
|
||||
# early-VM provisioning. Retry once after a short settle so
|
||||
# callers (provision_ca, etc.) don't have to handle it themselves.
|
||||
_SIGKILL_EXIT = 128 + 9
|
||||
|
||||
def exec(self, script: str, *, user: str = "node") -> ExecResult:
|
||||
"""Run a POSIX shell script as `user` (default `node`) and
|
||||
capture the result. Matches the docker backend's `exec`,
|
||||
@@ -141,14 +147,22 @@ class SmolmachinesBottle(Bottle):
|
||||
|
||||
`runuser -u <user> -- env ... /bin/sh -c <script>` switches UID
|
||||
without invoking a login shell, then sets HOME / USER and the
|
||||
bottle env in the child process."""
|
||||
bottle env in the child process.
|
||||
|
||||
Retries once on SIGKILL (exit 137) — libkrun occasionally
|
||||
kills short-lived execs during VM bring-up."""
|
||||
r = self._exec_raw(script, user=user)
|
||||
if r.returncode == self._SIGKILL_EXIT:
|
||||
time.sleep(1.0)
|
||||
r = self._exec_raw(script, user=user)
|
||||
return r
|
||||
|
||||
def _exec_raw(self, script: str, *, user: str = "node") -> ExecResult:
|
||||
argv = [
|
||||
"--", "runuser", "-u", user, "--",
|
||||
"env", *_env_assignments_for(user, self._guest_env),
|
||||
"/bin/sh", "-c", script,
|
||||
]
|
||||
# Call smolvm directly because this path needs the host-side
|
||||
# subprocess capture shape used by the Docker backend.
|
||||
r = subprocess.run(
|
||||
["smolvm", "machine", "exec", "--name", self.name] + argv,
|
||||
capture_output=True, text=True, check=False,
|
||||
|
||||
Reference in New Issue
Block a user