Add runtime-owned shell API boundary
- SHA
cc0fe0413333f54d5de2e84797d0da8867feee3d- Parents
-
803dde9 - Tree
008687d
cc0fe04
cc0fe0413333f54d5de2e84797d0da8867feee3d803dde9
008687d| Status | File | + | - |
|---|---|---|---|
| M |
src/loader/cli/main.py
|
14 | 31 |
| A |
src/loader/runtime/runtime_api.py
|
105 | 0 |
| M |
src/loader/ui/app.py
|
2 | 35 |
| M |
tests/test_cli_runtime_owner.py
|
35 | 20 |
src/loader/cli/main.pymodified@@ -39,6 +39,7 @@ from ..runtime.inspection import ( | |||
| 39 | ) | 39 | ) |
| 40 | from ..runtime.owner_metadata import format_runtime_owner_label | 40 | from ..runtime.owner_metadata import format_runtime_owner_label |
| 41 | from ..runtime.permissions import PermissionMode | 41 | from ..runtime.permissions import PermissionMode |
| 42 | +from ..runtime.runtime_api import RuntimeShellOwner, build_runtime_shell_owner | ||
| 42 | from ..runtime.workflow_timeline_read_model import ( | 43 | from ..runtime.workflow_timeline_read_model import ( |
| 43 | format_evidence_provenance_brief, | 44 | format_evidence_provenance_brief, |
| 44 | summarize_observed_verification, | 45 | summarize_observed_verification, |
@@ -188,31 +189,6 @@ def clean_response(text: str) -> str: | |||
| 188 | return text.strip() | 189 | return text.strip() |
| 189 | 190 | ||
| 190 | 191 | ||
| 191 | -def _build_cli_shell_owner( | ||
| 192 | - *, | ||
| 193 | - backend, | ||
| 194 | - registry, | ||
| 195 | - config, | ||
| 196 | - require_public_agent: bool, | ||
| 197 | -): | ||
| 198 | - """Build the CLI runtime owner for the requested integration path. | ||
| 199 | - | ||
| 200 | - Non-TUI CLI flows use the runtime-first internal handle so internal | ||
| 201 | - integrations stop depending on `Agent` by reflex. Public-shell construction | ||
| 202 | - remains available for explicit compatibility paths, but the CLI can choose a | ||
| 203 | - runtime-first owner even for interactive surfaces. | ||
| 204 | - """ | ||
| 205 | - | ||
| 206 | - if require_public_agent: | ||
| 207 | - from ..agent.loop import Agent | ||
| 208 | - | ||
| 209 | - return Agent(backend=backend, registry=registry, config=config) | ||
| 210 | - | ||
| 211 | - from ..runtime.runtime_handle import RuntimeHandle | ||
| 212 | - | ||
| 213 | - return RuntimeHandle(backend=backend, registry=registry, config=config) | ||
| 214 | - | ||
| 215 | - | ||
| 216 | @click.command() | 192 | @click.command() |
| 217 | @click.option("--model", "-m", default=None, help="Model to use (default: llama3.1:8b)") | 193 | @click.option("--model", "-m", default=None, help="Model to use (default: llama3.1:8b)") |
| 218 | @click.option("--select-model", "-s", is_flag=True, help="Interactively select model from available") | 194 | @click.option("--select-model", "-s", is_flag=True, help="Interactively select model from available") |
@@ -391,11 +367,11 @@ async def _main( | |||
| 391 | reasoning=reasoning_config, | 367 | reasoning=reasoning_config, |
| 392 | ) | 368 | ) |
| 393 | try: | 369 | try: |
| 394 | - shell_owner = _build_cli_shell_owner( | 370 | + shell_owner = build_runtime_shell_owner( |
| 395 | backend=llm, | 371 | backend=llm, |
| 396 | registry=registry, | 372 | registry=registry, |
| 397 | config=config, | 373 | config=config, |
| 398 | - require_public_agent=False, | 374 | + owner_kind="runtime", |
| 399 | ) | 375 | ) |
| 400 | except ValueError as exc: | 376 | except ValueError as exc: |
| 401 | console.print(f"[red]Permission policy error:[/red] {exc}") | 377 | console.print(f"[red]Permission policy error:[/red] {exc}") |
@@ -514,7 +490,11 @@ def _format_tool_args(args: dict | None) -> str: | |||
| 514 | return ", ".join(parts) | 490 | return ", ".join(parts) |
| 515 | 491 | ||
| 516 | 492 | ||
| 517 | -async def run_once(shell_owner, prompt: str, skip_confirmation: bool = False) -> None: | 493 | +async def run_once( |
| 494 | + shell_owner: RuntimeShellOwner, | ||
| 495 | + prompt: str, | ||
| 496 | + skip_confirmation: bool = False, | ||
| 497 | +) -> None: | ||
| 518 | """Run a single prompt through one shell-compatible runtime owner.""" | 498 | """Run a single prompt through one shell-compatible runtime owner.""" |
| 519 | import time | 499 | import time |
| 520 | 500 | ||
@@ -614,7 +594,10 @@ async def run_once(shell_owner, prompt: str, skip_confirmation: bool = False) -> | |||
| 614 | console.print("[red]Aborted.[/red]") | 594 | console.print("[red]Aborted.[/red]") |
| 615 | 595 | ||
| 616 | 596 | ||
| 617 | -async def run_interactive(shell_owner, skip_confirmation: bool = False) -> None: | 597 | +async def run_interactive( |
| 598 | + shell_owner: RuntimeShellOwner, | ||
| 599 | + skip_confirmation: bool = False, | ||
| 600 | +) -> None: | ||
| 618 | """Run the simple interactive chat loop for one shell-compatible owner.""" | 601 | """Run the simple interactive chat loop for one shell-compatible owner.""" |
| 619 | import os | 602 | import os |
| 620 | 603 | ||
@@ -1283,7 +1266,7 @@ async def _explore_main( | |||
| 1283 | set_last_model(model) | 1266 | set_last_model(model) |
| 1284 | 1267 | ||
| 1285 | try: | 1268 | try: |
| 1286 | - shell_owner = _build_cli_shell_owner( | 1269 | + shell_owner = build_runtime_shell_owner( |
| 1287 | backend=llm, | 1270 | backend=llm, |
| 1288 | registry=None, | 1271 | registry=None, |
| 1289 | config=AgentConfig( | 1272 | config=AgentConfig( |
@@ -1292,7 +1275,7 @@ async def _explore_main( | |||
| 1292 | permission_mode=PermissionMode.READ_ONLY, | 1275 | permission_mode=PermissionMode.READ_ONLY, |
| 1293 | stream=False, | 1276 | stream=False, |
| 1294 | ), | 1277 | ), |
| 1295 | - require_public_agent=False, | 1278 | + owner_kind="runtime", |
| 1296 | ) | 1279 | ) |
| 1297 | except ValueError as exc: | 1280 | except ValueError as exc: |
| 1298 | console.print(f"[red]Permission policy error:[/red] {exc}") | 1281 | console.print(f"[red]Permission policy error:[/red] {exc}") |
src/loader/runtime/runtime_api.pyadded@@ -0,0 +1,105 @@ | |||
| 1 | +"""Runtime-owned public shell contract and owner builder.""" | ||
| 2 | + | ||
| 3 | +from __future__ import annotations | ||
| 4 | + | ||
| 5 | +from collections.abc import AsyncIterator, Awaitable, Callable | ||
| 6 | +from pathlib import Path | ||
| 7 | +from typing import Literal, Protocol | ||
| 8 | + | ||
| 9 | +from ..context.project import ProjectContext | ||
| 10 | +from ..tools.base import ToolRegistry | ||
| 11 | +from .capabilities import CapabilityProfile | ||
| 12 | +from .events import AgentEvent | ||
| 13 | +from .session import ConversationSession | ||
| 14 | + | ||
| 15 | +RuntimeOwnerKind = Literal["runtime", "public-compat"] | ||
| 16 | + | ||
| 17 | + | ||
| 18 | +class RuntimeShellOwner(Protocol): | ||
| 19 | + """Narrow public shell contract shared by CLI, UI, and integrations.""" | ||
| 20 | + | ||
| 21 | + backend: object | ||
| 22 | + registry: ToolRegistry | ||
| 23 | + capability_profile: CapabilityProfile | ||
| 24 | + safeguards: object | ||
| 25 | + session: ConversationSession | ||
| 26 | + project_context: ProjectContext | None | ||
| 27 | + workflow_mode: str | ||
| 28 | + active_permission_mode: str | ||
| 29 | + use_react: bool | ||
| 30 | + is_running: bool | ||
| 31 | + | ||
| 32 | + def resume_session(self, session_id: str | None = None) -> bool: | ||
| 33 | + """Resume the latest or named persisted session.""" | ||
| 34 | + | ||
| 35 | + def steer(self, message: str) -> bool: | ||
| 36 | + """Queue one steering message while the owner is running.""" | ||
| 37 | + | ||
| 38 | + def refresh_capability_profile(self) -> None: | ||
| 39 | + """Refresh the active capability profile.""" | ||
| 40 | + | ||
| 41 | + async def run( | ||
| 42 | + self, | ||
| 43 | + user_message: str, | ||
| 44 | + on_event: ( | ||
| 45 | + Callable[[AgentEvent], None] | ||
| 46 | + | Callable[[AgentEvent], Awaitable[None]] | ||
| 47 | + | None | ||
| 48 | + ) = None, | ||
| 49 | + on_confirmation: Callable[[str, str, str], Awaitable[bool]] | None = None, | ||
| 50 | + on_user_question: Callable[[str, list[str] | None], Awaitable[str]] | None = None, | ||
| 51 | + use_plan: bool | None = None, | ||
| 52 | + ) -> str: | ||
| 53 | + """Run one user message through the shell owner.""" | ||
| 54 | + | ||
| 55 | + async def run_streaming( | ||
| 56 | + self, | ||
| 57 | + user_message: str, | ||
| 58 | + ) -> AsyncIterator[AgentEvent]: | ||
| 59 | + """Yield the streamed event sequence for one user message.""" | ||
| 60 | + | ||
| 61 | + async def run_explore( | ||
| 62 | + self, | ||
| 63 | + user_message: str, | ||
| 64 | + on_event: ( | ||
| 65 | + Callable[[AgentEvent], None] | ||
| 66 | + | Callable[[AgentEvent], Awaitable[None]] | ||
| 67 | + | None | ||
| 68 | + ) = None, | ||
| 69 | + *, | ||
| 70 | + fresh: bool = False, | ||
| 71 | + ) -> str: | ||
| 72 | + """Run one read-only explore query through the shell owner.""" | ||
| 73 | + | ||
| 74 | + def clear_history(self) -> None: | ||
| 75 | + """Reset the owner history.""" | ||
| 76 | + | ||
| 77 | + | ||
| 78 | +def build_runtime_shell_owner( | ||
| 79 | + *, | ||
| 80 | + backend, | ||
| 81 | + registry, | ||
| 82 | + config, | ||
| 83 | + project_root: Path | str | None = None, | ||
| 84 | + owner_kind: RuntimeOwnerKind = "runtime", | ||
| 85 | +) -> RuntimeShellOwner: | ||
| 86 | + """Build the shared shell owner for runtime-first or compatibility paths.""" | ||
| 87 | + | ||
| 88 | + if owner_kind == "public-compat": | ||
| 89 | + from ..agent.loop import Agent | ||
| 90 | + | ||
| 91 | + return Agent( | ||
| 92 | + backend=backend, | ||
| 93 | + registry=registry, | ||
| 94 | + config=config, | ||
| 95 | + project_root=project_root, | ||
| 96 | + ) | ||
| 97 | + | ||
| 98 | + from .runtime_handle import RuntimeHandle | ||
| 99 | + | ||
| 100 | + return RuntimeHandle( | ||
| 101 | + backend=backend, | ||
| 102 | + registry=registry, | ||
| 103 | + config=config, | ||
| 104 | + project_root=project_root, | ||
| 105 | + ) | ||
src/loader/ui/app.pymodified@@ -2,9 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | import asyncio | 3 | import asyncio |
| 4 | import time | 4 | import time |
| 5 | -from collections.abc import Awaitable, Callable | ||
| 6 | from pathlib import Path | 5 | from pathlib import Path |
| 7 | -from typing import Protocol | ||
| 8 | 6 | ||
| 9 | from rich.markup import escape | 7 | from rich.markup import escape |
| 10 | from textual import work | 8 | from textual import work |
@@ -16,6 +14,7 @@ from textual.widgets import Footer, Input, Static | |||
| 16 | from textual.worker import Worker, get_current_worker | 14 | from textual.worker import Worker, get_current_worker |
| 17 | 15 | ||
| 18 | from ..runtime.events import AgentEvent | 16 | from ..runtime.events import AgentEvent |
| 17 | +from ..runtime.runtime_api import RuntimeShellOwner | ||
| 19 | from .adapter import ( | 18 | from .adapter import ( |
| 20 | ArtifactCreated, | 19 | ArtifactCreated, |
| 21 | ClearStream, | 20 | ClearStream, |
@@ -52,38 +51,6 @@ from .widgets import ( | |||
| 52 | ) | 51 | ) |
| 53 | 52 | ||
| 54 | 53 | ||
| 55 | -class LoaderUIShellOwner(Protocol): | ||
| 56 | - """Small shell-owner contract used by the Textual UI.""" | ||
| 57 | - | ||
| 58 | - backend: object | ||
| 59 | - capability_profile: object | ||
| 60 | - safeguards: object | ||
| 61 | - is_running: bool | ||
| 62 | - | ||
| 63 | - def steer(self, message: str) -> bool: | ||
| 64 | - """Queue one steering message while the owner is running.""" | ||
| 65 | - | ||
| 66 | - def refresh_capability_profile(self) -> None: | ||
| 67 | - """Refresh the active capability profile.""" | ||
| 68 | - | ||
| 69 | - async def run( | ||
| 70 | - self, | ||
| 71 | - user_message: str, | ||
| 72 | - on_event: ( | ||
| 73 | - Callable[[AgentEvent], None] | ||
| 74 | - | Callable[[AgentEvent], Awaitable[None]] | ||
| 75 | - | None | ||
| 76 | - ) = None, | ||
| 77 | - on_confirmation: Callable[[str, str, str], Awaitable[bool]] | None = None, | ||
| 78 | - on_user_question: Callable[[str, list[str] | None], Awaitable[str]] | None = None, | ||
| 79 | - use_plan: bool | None = None, | ||
| 80 | - ) -> str: | ||
| 81 | - """Run one user message through the shell owner.""" | ||
| 82 | - | ||
| 83 | - def clear_history(self) -> None: | ||
| 84 | - """Reset the owner history.""" | ||
| 85 | - | ||
| 86 | - | ||
| 87 | class LoaderApp(App): | 54 | class LoaderApp(App): |
| 88 | """Main Textual application for Loader.""" | 55 | """Main Textual application for Loader.""" |
| 89 | 56 | ||
@@ -100,7 +67,7 @@ class LoaderApp(App): | |||
| 100 | 67 | ||
| 101 | def __init__( | 68 | def __init__( |
| 102 | self, | 69 | self, |
| 103 | - shell_owner: LoaderUIShellOwner, | 70 | + shell_owner: RuntimeShellOwner, |
| 104 | model_name: str = "", | 71 | model_name: str = "", |
| 105 | mode: str = "Native", | 72 | mode: str = "Native", |
| 106 | capability_profile: str = "", | 73 | capability_profile: str = "", |
tests/test_cli_runtime_owner.pymodified@@ -9,6 +9,7 @@ import pytest | |||
| 9 | 9 | ||
| 10 | import loader.agent.loop as agent_loop_module | 10 | import loader.agent.loop as agent_loop_module |
| 11 | import loader.cli.main as cli_main_module | 11 | import loader.cli.main as cli_main_module |
| 12 | +import loader.runtime.runtime_api as runtime_api_module | ||
| 12 | import loader.runtime.runtime_handle as runtime_handle_module | 13 | import loader.runtime.runtime_handle as runtime_handle_module |
| 13 | from loader.agent.loop import AgentConfig | 14 | from loader.agent.loop import AgentConfig |
| 14 | 15 | ||
@@ -34,7 +35,7 @@ def _install_fake_ollama_module(monkeypatch: pytest.MonkeyPatch) -> None: | |||
| 34 | monkeypatch.setitem(sys.modules, "loader.llm.ollama", module) | 35 | monkeypatch.setitem(sys.modules, "loader.llm.ollama", module) |
| 35 | 36 | ||
| 36 | 37 | ||
| 37 | -def test_build_cli_shell_owner_uses_runtime_handle_for_internal_paths( | 38 | +def test_build_runtime_shell_owner_uses_runtime_handle_for_runtime_paths( |
| 38 | monkeypatch: pytest.MonkeyPatch, | 39 | monkeypatch: pytest.MonkeyPatch, |
| 39 | ) -> None: | 40 | ) -> None: |
| 40 | seen: list[dict[str, object]] = [] | 41 | seen: list[dict[str, object]] = [] |
@@ -50,18 +51,25 @@ def test_build_cli_shell_owner_uses_runtime_handle_for_internal_paths( | |||
| 50 | monkeypatch.setattr(runtime_handle_module, "RuntimeHandle", FakeHandle) | 51 | monkeypatch.setattr(runtime_handle_module, "RuntimeHandle", FakeHandle) |
| 51 | monkeypatch.setattr(agent_loop_module, "Agent", FakeAgent) | 52 | monkeypatch.setattr(agent_loop_module, "Agent", FakeAgent) |
| 52 | 53 | ||
| 53 | - owner = cli_main_module._build_cli_shell_owner( | 54 | + owner = runtime_api_module.build_runtime_shell_owner( |
| 54 | backend="backend", | 55 | backend="backend", |
| 55 | registry="registry", | 56 | registry="registry", |
| 56 | config="config", | 57 | config="config", |
| 57 | - require_public_agent=False, | 58 | + owner_kind="runtime", |
| 58 | ) | 59 | ) |
| 59 | 60 | ||
| 60 | assert isinstance(owner, FakeHandle) | 61 | assert isinstance(owner, FakeHandle) |
| 61 | - assert seen == [{"backend": "backend", "registry": "registry", "config": "config"}] | 62 | + assert seen == [ |
| 63 | + { | ||
| 64 | + "backend": "backend", | ||
| 65 | + "registry": "registry", | ||
| 66 | + "config": "config", | ||
| 67 | + "project_root": None, | ||
| 68 | + } | ||
| 69 | + ] | ||
| 62 | 70 | ||
| 63 | 71 | ||
| 64 | -def test_build_cli_shell_owner_uses_agent_for_public_paths( | 72 | +def test_build_runtime_shell_owner_uses_agent_for_public_compat_paths( |
| 65 | monkeypatch: pytest.MonkeyPatch, | 73 | monkeypatch: pytest.MonkeyPatch, |
| 66 | ) -> None: | 74 | ) -> None: |
| 67 | seen: list[dict[str, object]] = [] | 75 | seen: list[dict[str, object]] = [] |
@@ -77,15 +85,22 @@ def test_build_cli_shell_owner_uses_agent_for_public_paths( | |||
| 77 | monkeypatch.setattr(runtime_handle_module, "RuntimeHandle", FakeHandle) | 85 | monkeypatch.setattr(runtime_handle_module, "RuntimeHandle", FakeHandle) |
| 78 | monkeypatch.setattr(agent_loop_module, "Agent", FakeAgent) | 86 | monkeypatch.setattr(agent_loop_module, "Agent", FakeAgent) |
| 79 | 87 | ||
| 80 | - owner = cli_main_module._build_cli_shell_owner( | 88 | + owner = runtime_api_module.build_runtime_shell_owner( |
| 81 | backend="backend", | 89 | backend="backend", |
| 82 | registry="registry", | 90 | registry="registry", |
| 83 | config="config", | 91 | config="config", |
| 84 | - require_public_agent=True, | 92 | + owner_kind="public-compat", |
| 85 | ) | 93 | ) |
| 86 | 94 | ||
| 87 | assert isinstance(owner, FakeAgent) | 95 | assert isinstance(owner, FakeAgent) |
| 88 | - assert seen == [{"backend": "backend", "registry": "registry", "config": "config"}] | 96 | + assert seen == [ |
| 97 | + { | ||
| 98 | + "backend": "backend", | ||
| 99 | + "registry": "registry", | ||
| 100 | + "config": "config", | ||
| 101 | + "project_root": None, | ||
| 102 | + } | ||
| 103 | + ] | ||
| 89 | 104 | ||
| 90 | 105 | ||
| 91 | @pytest.mark.asyncio | 106 | @pytest.mark.asyncio |
@@ -125,18 +140,18 @@ async def test_main_uses_runtime_first_owner_for_tui_launch( | |||
| 125 | fake_ui_app.LoaderApp = FakeApp | 140 | fake_ui_app.LoaderApp = FakeApp |
| 126 | monkeypatch.setitem(sys.modules, "loader.ui.app", fake_ui_app) | 141 | monkeypatch.setitem(sys.modules, "loader.ui.app", fake_ui_app) |
| 127 | 142 | ||
| 128 | - def fake_build_owner(*, backend, registry, config, require_public_agent): | 143 | + def fake_build_owner(*, backend, registry, config, owner_kind): |
| 129 | owner_calls.append( | 144 | owner_calls.append( |
| 130 | { | 145 | { |
| 131 | "backend": backend, | 146 | "backend": backend, |
| 132 | "registry": registry, | 147 | "registry": registry, |
| 133 | "config": config, | 148 | "config": config, |
| 134 | - "require_public_agent": require_public_agent, | 149 | + "owner_kind": owner_kind, |
| 135 | } | 150 | } |
| 136 | ) | 151 | ) |
| 137 | return fake_owner | 152 | return fake_owner |
| 138 | 153 | ||
| 139 | - monkeypatch.setattr(cli_main_module, "_build_cli_shell_owner", fake_build_owner) | 154 | + monkeypatch.setattr(cli_main_module, "build_runtime_shell_owner", fake_build_owner) |
| 140 | 155 | ||
| 141 | await cli_main_module._main( | 156 | await cli_main_module._main( |
| 142 | model="fake-model", | 157 | model="fake-model", |
@@ -162,7 +177,7 @@ async def test_main_uses_runtime_first_owner_for_tui_launch( | |||
| 162 | prompt=None, | 177 | prompt=None, |
| 163 | ) | 178 | ) |
| 164 | 179 | ||
| 165 | - assert owner_calls and owner_calls[0]["require_public_agent"] is False | 180 | + assert owner_calls and owner_calls[0]["owner_kind"] == "runtime" |
| 166 | assert app_calls[0]["shell_owner"] is fake_owner | 181 | assert app_calls[0]["shell_owner"] is fake_owner |
| 167 | assert app_calls[-1] == {"ran": True} | 182 | assert app_calls[-1] == {"ran": True} |
| 168 | 183 | ||
@@ -193,13 +208,13 @@ async def test_main_uses_runtime_first_owner_for_single_prompt( | |||
| 193 | lambda: SimpleNamespace(skip_confirmation=False), | 208 | lambda: SimpleNamespace(skip_confirmation=False), |
| 194 | ) | 209 | ) |
| 195 | 210 | ||
| 196 | - def fake_build_owner(*, backend, registry, config, require_public_agent): | 211 | + def fake_build_owner(*, backend, registry, config, owner_kind): |
| 197 | seen.append( | 212 | seen.append( |
| 198 | { | 213 | { |
| 199 | "backend": backend, | 214 | "backend": backend, |
| 200 | "registry": registry, | 215 | "registry": registry, |
| 201 | "config": config, | 216 | "config": config, |
| 202 | - "require_public_agent": require_public_agent, | 217 | + "owner_kind": owner_kind, |
| 203 | } | 218 | } |
| 204 | ) | 219 | ) |
| 205 | return fake_owner | 220 | return fake_owner |
@@ -211,7 +226,7 @@ async def test_main_uses_runtime_first_owner_for_single_prompt( | |||
| 211 | captured["prompt"] = prompt | 226 | captured["prompt"] = prompt |
| 212 | captured["skip_confirmation"] = skip_confirmation | 227 | captured["skip_confirmation"] = skip_confirmation |
| 213 | 228 | ||
| 214 | - monkeypatch.setattr(cli_main_module, "_build_cli_shell_owner", fake_build_owner) | 229 | + monkeypatch.setattr(cli_main_module, "build_runtime_shell_owner", fake_build_owner) |
| 215 | monkeypatch.setattr(cli_main_module, "run_once", fake_run_once) | 230 | monkeypatch.setattr(cli_main_module, "run_once", fake_run_once) |
| 216 | 231 | ||
| 217 | await cli_main_module._main( | 232 | await cli_main_module._main( |
@@ -238,7 +253,7 @@ async def test_main_uses_runtime_first_owner_for_single_prompt( | |||
| 238 | prompt="Summarize the runtime-first shell path.", | 253 | prompt="Summarize the runtime-first shell path.", |
| 239 | ) | 254 | ) |
| 240 | 255 | ||
| 241 | - assert seen and seen[0]["require_public_agent"] is False | 256 | + assert seen and seen[0]["owner_kind"] == "runtime" |
| 242 | assert isinstance(seen[0]["config"], AgentConfig) | 257 | assert isinstance(seen[0]["config"], AgentConfig) |
| 243 | assert captured == { | 258 | assert captured == { |
| 244 | "owner": fake_owner, | 259 | "owner": fake_owner, |
@@ -267,18 +282,18 @@ async def test_explore_main_uses_runtime_first_owner( | |||
| 267 | 282 | ||
| 268 | owner_calls: list[dict[str, object]] = [] | 283 | owner_calls: list[dict[str, object]] = [] |
| 269 | 284 | ||
| 270 | - def fake_build_owner(*, backend, registry, config, require_public_agent): | 285 | + def fake_build_owner(*, backend, registry, config, owner_kind): |
| 271 | owner_calls.append( | 286 | owner_calls.append( |
| 272 | { | 287 | { |
| 273 | "backend": backend, | 288 | "backend": backend, |
| 274 | "registry": registry, | 289 | "registry": registry, |
| 275 | "config": config, | 290 | "config": config, |
| 276 | - "require_public_agent": require_public_agent, | 291 | + "owner_kind": owner_kind, |
| 277 | } | 292 | } |
| 278 | ) | 293 | ) |
| 279 | return FakeExploreOwner() | 294 | return FakeExploreOwner() |
| 280 | 295 | ||
| 281 | - monkeypatch.setattr(cli_main_module, "_build_cli_shell_owner", fake_build_owner) | 296 | + monkeypatch.setattr(cli_main_module, "build_runtime_shell_owner", fake_build_owner) |
| 282 | 297 | ||
| 283 | await cli_main_module._explore_main( | 298 | await cli_main_module._explore_main( |
| 284 | model="fake-model", | 299 | model="fake-model", |
@@ -293,7 +308,7 @@ async def test_explore_main_uses_runtime_first_owner( | |||
| 293 | prompt="Where should I start?", | 308 | prompt="Where should I start?", |
| 294 | ) | 309 | ) |
| 295 | 310 | ||
| 296 | - assert owner_calls and owner_calls[0]["require_public_agent"] is False | 311 | + assert owner_calls and owner_calls[0]["owner_kind"] == "runtime" |
| 297 | assert isinstance(owner_calls[0]["config"], AgentConfig) | 312 | assert isinstance(owner_calls[0]["config"], AgentConfig) |
| 298 | assert len(seen) == 1 | 313 | assert len(seen) == 1 |
| 299 | assert seen[0]["prompt"] == "Where should I start?" | 314 | assert seen[0]["prompt"] == "Where should I start?" |