fix(types): add BottleSpec.loaded_manifest to satisfy pyright on union type
BottleSpec.manifest is ManifestIndex | Manifest (pre/post _validate()). Downstream code always runs post-validate so it needs Manifest, but pyright flagged every .agent/.bottle access. The new loaded_manifest property asserts isinstance and returns Manifest, giving pyright a narrowed type without scattering type: ignore everywhere. Also remove unused Manifest imports from test files and annotate the _index() helper in test_manifest_agent_git_user.
This commit is contained in:
@@ -240,7 +240,7 @@ class AgentProvider(ABC):
|
|||||||
BottleBackend.provision_workspace against the running bottle."""
|
BottleBackend.provision_workspace against the running bottle."""
|
||||||
from .log import info
|
from .log import info
|
||||||
|
|
||||||
manifest_bottle = plan.spec.manifest.bottle
|
manifest_bottle = plan.spec.loaded_manifest.bottle
|
||||||
if manifest_bottle.git:
|
if manifest_bottle.git:
|
||||||
from .git_gate import GIT_GATE_HOSTNAME, git_gate_render_gitconfig
|
from .git_gate import GIT_GATE_HOSTNAME, git_gate_render_gitconfig
|
||||||
gate_host = getattr(plan, "git_gate_insteadof_host", GIT_GATE_HOSTNAME)
|
gate_host = getattr(plan, "git_gate_insteadof_host", GIT_GATE_HOSTNAME)
|
||||||
|
|||||||
@@ -65,6 +65,13 @@ class BottleSpec:
|
|||||||
agent_name: str
|
agent_name: str
|
||||||
copy_cwd: bool
|
copy_cwd: bool
|
||||||
user_cwd: str
|
user_cwd: str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loaded_manifest(self) -> Manifest:
|
||||||
|
assert isinstance(self.manifest, Manifest), (
|
||||||
|
"spec.manifest is still a ManifestIndex — call _validate() first"
|
||||||
|
)
|
||||||
|
return self.manifest
|
||||||
# PRD 0016 follow-up: when set, the backend's prepare step uses
|
# PRD 0016 follow-up: when set, the backend's prepare step uses
|
||||||
# this identity instead of minting a fresh one — the resume path
|
# this identity instead of minting a fresh one — the resume path
|
||||||
# (`cli.py resume <identity>`) sets this to continue an existing
|
# (`cli.py resume <identity>`) sets this to continue an existing
|
||||||
@@ -112,7 +119,7 @@ class BottlePlan(ABC):
|
|||||||
"""Render the y/N preflight summary to stderr."""
|
"""Render the y/N preflight summary to stderr."""
|
||||||
del remote_control
|
del remote_control
|
||||||
spec = self.spec
|
spec = self.spec
|
||||||
manifest = spec.manifest # type: ignore[assignment]
|
manifest = spec.loaded_manifest
|
||||||
agent = manifest.agent
|
agent = manifest.agent
|
||||||
bottle = manifest.bottle
|
bottle = manifest.bottle
|
||||||
|
|
||||||
@@ -293,7 +300,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
|
|
||||||
self._preflight()
|
self._preflight()
|
||||||
|
|
||||||
manifest = spec.manifest # type: ignore[assignment]
|
manifest = spec.loaded_manifest
|
||||||
manifest_bottle = manifest.bottle
|
manifest_bottle = manifest.bottle
|
||||||
manifest_agent_provider = manifest_bottle.agent_provider
|
manifest_agent_provider = manifest_bottle.agent_provider
|
||||||
agent_provider = get_provider(manifest_agent_provider.template)
|
agent_provider = get_provider(manifest_agent_provider.template)
|
||||||
@@ -364,7 +371,10 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
selected agent. Subclasses with additional preconditions should
|
selected agent. Subclasses with additional preconditions should
|
||||||
override and call `super()._validate(spec)` first, using the
|
override and call `super()._validate(spec)` first, using the
|
||||||
returned spec for further checks."""
|
returned spec for further checks."""
|
||||||
manifest = spec.manifest.load_for_agent(spec.agent_name) # type: ignore[union-attr]
|
assert isinstance(spec.manifest, ManifestIndex), (
|
||||||
|
"_validate() called on a spec whose manifest is already loaded"
|
||||||
|
)
|
||||||
|
manifest = spec.manifest.load_for_agent(spec.agent_name)
|
||||||
spec = replace(spec, manifest=manifest)
|
spec = replace(spec, manifest=manifest)
|
||||||
agent = manifest.agent
|
agent = manifest.agent
|
||||||
self._validate_skills(agent.skills)
|
self._validate_skills(agent.skills)
|
||||||
@@ -384,7 +394,7 @@ class BottleBackend(ABC, Generic[PlanT, CleanupT]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _validate_agent_provider_dockerfile(self, spec: BottleSpec) -> None:
|
def _validate_agent_provider_dockerfile(self, spec: BottleSpec) -> None:
|
||||||
manifest = spec.manifest # type: ignore[assignment]
|
manifest = spec.loaded_manifest
|
||||||
bottle = manifest.bottle
|
bottle = manifest.bottle
|
||||||
dockerfile = bottle.agent_provider.dockerfile
|
dockerfile = bottle.agent_provider.dockerfile
|
||||||
if not dockerfile:
|
if not dockerfile:
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ def launch(
|
|||||||
Teardown on exit."""
|
Teardown on exit."""
|
||||||
stack = ExitStack()
|
stack = ExitStack()
|
||||||
|
|
||||||
_bottle_for_revoke = plan.spec.manifest.bottle
|
_bottle_for_revoke = plan.spec.loaded_manifest.bottle
|
||||||
_git_gate_dir_for_revoke = git_gate_state_dir(plan.slug)
|
_git_gate_dir_for_revoke = git_gate_state_dir(plan.slug)
|
||||||
|
|
||||||
def teardown() -> None:
|
def teardown() -> None:
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def launch(
|
|||||||
) -> Generator[MacosContainerBottle, None, None]:
|
) -> Generator[MacosContainerBottle, None, None]:
|
||||||
"""Build, run, provision, and yield an Apple Container bottle."""
|
"""Build, run, provision, and yield an Apple Container bottle."""
|
||||||
stack = ExitStack()
|
stack = ExitStack()
|
||||||
bottle_for_revoke = plan.spec.manifest.bottle
|
bottle_for_revoke = plan.spec.loaded_manifest.bottle
|
||||||
git_gate_dir_for_revoke = git_gate_state_dir(plan.slug)
|
git_gate_dir_for_revoke = git_gate_state_dir(plan.slug)
|
||||||
|
|
||||||
def teardown() -> None:
|
def teardown() -> None:
|
||||||
|
|||||||
@@ -69,8 +69,7 @@ def write_launch_metadata(
|
|||||||
def prepare_agent_state_dir(slug: str, spec: BottleSpec) -> tuple[Path, Path]:
|
def prepare_agent_state_dir(slug: str, spec: BottleSpec) -> tuple[Path, Path]:
|
||||||
"""Create the agent state subdir, write the prompt file.
|
"""Create the agent state subdir, write the prompt file.
|
||||||
Returns (agent_dir, prompt_file)."""
|
Returns (agent_dir, prompt_file)."""
|
||||||
manifest = spec.manifest # type: ignore[assignment]
|
agent = spec.loaded_manifest.agent
|
||||||
agent = manifest.agent
|
|
||||||
agent_dir = agent_state_dir(slug)
|
agent_dir = agent_state_dir(slug)
|
||||||
agent_dir.mkdir(parents=True, exist_ok=True)
|
agent_dir.mkdir(parents=True, exist_ok=True)
|
||||||
prompt_file = agent_dir / "prompt.txt"
|
prompt_file = agent_dir / "prompt.txt"
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ def _teardown_smolmachines(
|
|||||||
except BaseException as exc: # noqa: W0718 — teardown must not fail
|
except BaseException as exc: # noqa: W0718 — teardown must not fail
|
||||||
teardown_exc = exc
|
teardown_exc = exc
|
||||||
warn(f"smolmachines teardown failed: {exc!r}")
|
warn(f"smolmachines teardown failed: {exc!r}")
|
||||||
bottle = plan.spec.manifest.bottle
|
bottle = plan.spec.loaded_manifest.bottle
|
||||||
revoke_git_gate_provisioned_keys(bottle, git_gate_state_dir(plan.slug))
|
revoke_git_gate_provisioned_keys(bottle, git_gate_state_dir(plan.slug))
|
||||||
if teardown_exc is not None:
|
if teardown_exc is not None:
|
||||||
raise teardown_exc
|
raise teardown_exc
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
when the agent has no skills."""
|
when the agent has no skills."""
|
||||||
from ...backend.util import host_skill_dir
|
from ...backend.util import host_skill_dir
|
||||||
|
|
||||||
agent = plan.spec.manifest.agent
|
agent = plan.spec.loaded_manifest.agent
|
||||||
if not agent.skills:
|
if not agent.skills:
|
||||||
return
|
return
|
||||||
skills_dir = _skills_dir(plan.guest_home)
|
skills_dir = _skills_dir(plan.guest_home)
|
||||||
@@ -240,7 +240,7 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
f"chown node:node {prompt_path} && chmod 600 {prompt_path}",
|
f"chown node:node {prompt_path} && chmod 600 {prompt_path}",
|
||||||
user="root",
|
user="root",
|
||||||
)
|
)
|
||||||
agent = plan.spec.manifest.agent
|
agent = plan.spec.loaded_manifest.agent
|
||||||
return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None
|
return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None
|
||||||
|
|
||||||
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
skills."""
|
skills."""
|
||||||
from ...backend.util import host_skill_dir
|
from ...backend.util import host_skill_dir
|
||||||
|
|
||||||
agent = plan.spec.manifest.agent
|
agent = plan.spec.loaded_manifest.agent
|
||||||
if not agent.skills:
|
if not agent.skills:
|
||||||
return
|
return
|
||||||
skills_dir = _skills_dir(plan.guest_home)
|
skills_dir = _skills_dir(plan.guest_home)
|
||||||
@@ -206,7 +206,7 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
f"chown node:node {prompt_path} && chmod 600 {prompt_path}",
|
f"chown node:node {prompt_path} && chmod 600 {prompt_path}",
|
||||||
user="root",
|
user="root",
|
||||||
)
|
)
|
||||||
agent = plan.spec.manifest.agent
|
agent = plan.spec.loaded_manifest.agent
|
||||||
return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None
|
return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None
|
||||||
|
|
||||||
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ class PiAgentProvider(AgentProvider):
|
|||||||
def provision_skills(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
def provision_skills(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
||||||
from ...backend.util import host_skill_dir
|
from ...backend.util import host_skill_dir
|
||||||
|
|
||||||
agent = plan.spec.manifest.agent
|
agent = plan.spec.loaded_manifest.agent
|
||||||
if not agent.skills:
|
if not agent.skills:
|
||||||
return
|
return
|
||||||
skills_dir = _skills_dir(plan.guest_home)
|
skills_dir = _skills_dir(plan.guest_home)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
|||||||
from bot_bottle.contrib.claude.agent_provider import ClaudeAgentProvider
|
from bot_bottle.contrib.claude.agent_provider import ClaudeAgentProvider
|
||||||
from bot_bottle.egress import EgressPlan
|
from bot_bottle.egress import EgressPlan
|
||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest, ManifestIndex
|
from bot_bottle.manifest import ManifestIndex
|
||||||
from bot_bottle.supervise import SupervisePlan
|
from bot_bottle.supervise import SupervisePlan
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
|||||||
from bot_bottle.contrib.codex.agent_provider import CodexAgentProvider
|
from bot_bottle.contrib.codex.agent_provider import CodexAgentProvider
|
||||||
from bot_bottle.egress import EgressPlan
|
from bot_bottle.egress import EgressPlan
|
||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest, ManifestIndex
|
from bot_bottle.manifest import ManifestIndex
|
||||||
from bot_bottle.supervise import SupervisePlan
|
from bot_bottle.supervise import SupervisePlan
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
|||||||
from bot_bottle.contrib.pi.agent_provider import PiAgentProvider
|
from bot_bottle.contrib.pi.agent_provider import PiAgentProvider
|
||||||
from bot_bottle.egress import EgressPlan
|
from bot_bottle.egress import EgressPlan
|
||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest, ManifestIndex
|
from bot_bottle.manifest import ManifestIndex
|
||||||
|
|
||||||
|
|
||||||
_URL = "http://supervise:9100/"
|
_URL = "http://supervise:9100/"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from bot_bottle.backend import Bottle, BottleSpec, ExecResult
|
|||||||
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
from bot_bottle.backend.docker.bottle_plan import DockerBottlePlan
|
||||||
from bot_bottle.egress import EgressPlan
|
from bot_bottle.egress import EgressPlan
|
||||||
from bot_bottle.git_gate import GitGatePlan
|
from bot_bottle.git_gate import GitGatePlan
|
||||||
from bot_bottle.manifest import Manifest, ManifestIndex
|
from bot_bottle.manifest import ManifestIndex
|
||||||
|
|
||||||
|
|
||||||
class _Provider(AgentProvider):
|
class _Provider(AgentProvider):
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ def _manifest(*, bottle_user=None, agent_git=None) -> Manifest: # type: ignore
|
|||||||
}).load_for_agent("impl")
|
}).load_for_agent("impl")
|
||||||
|
|
||||||
|
|
||||||
def _index(*, bottle_user=None, agent_git=None) -> ManifestIndex:
|
def _index(*, bottle_user: dict[str, object] | None = None, agent_git: dict[str, object] | None = None) -> ManifestIndex:
|
||||||
"""Build an index with one agent 'impl' without loading it."""
|
"""Build an index with one agent 'impl' without loading it."""
|
||||||
bottle: dict = {} # type: ignore
|
bottle: dict = {} # type: ignore
|
||||||
if bottle_user is not None:
|
if bottle_user is not None:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import textwrap
|
|||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from bot_bottle.manifest import ManifestError, Manifest, ManifestIndex
|
from bot_bottle.manifest import ManifestError, ManifestIndex
|
||||||
|
|
||||||
|
|
||||||
def _write(p: Path, text: str) -> None:
|
def _write(p: Path, text: str) -> None:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from bot_bottle.backend.smolmachines.launch import _bundle_launch_spec
|
|||||||
from bot_bottle.backend.util import AGENT_CA_PATH
|
from bot_bottle.backend.util import AGENT_CA_PATH
|
||||||
from bot_bottle.egress import EgressPlan, EgressRoute
|
from bot_bottle.egress import EgressPlan, EgressRoute
|
||||||
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
from bot_bottle.git_gate import GitGatePlan, GitGateUpstream
|
||||||
from bot_bottle.manifest import ManifestGitEntry, ManifestKeyConfig, Manifest, ManifestIndex
|
from bot_bottle.manifest import ManifestGitEntry, ManifestKeyConfig, ManifestIndex
|
||||||
from bot_bottle.supervise import SupervisePlan
|
from bot_bottle.supervise import SupervisePlan
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user