diff --git a/src/tau_coding/extensions/api.py b/src/tau_coding/extensions/api.py index 192e5f415..9b3ed6431 100644 --- a/src/tau_coding/extensions/api.py +++ b/src/tau_coding/extensions/api.py @@ -20,6 +20,7 @@ from tau_coding.extensions.providers import DynamicProvider from tau_coding.extensions.runtime import ExtensionRuntime from tau_coding.local_backends import LocalBackend + from tau_coding.paths import TauPaths from tau_coding.tui.config import TuiTheme AGENT_EVENT_TYPES: frozenset[str] = frozenset( @@ -890,6 +891,12 @@ def cwd(self) -> Path: self._generation.assert_active() return self._runtime.session_view.cwd + @property + def paths(self) -> TauPaths: + """Return the resolved Tau filesystem paths for this session.""" + self._generation.assert_active() + return self._runtime.paths + @property def model(self) -> str: """Return the active model name.""" diff --git a/src/tau_coding/extensions/runtime.py b/src/tau_coding/extensions/runtime.py index adbc4c590..41bd13938 100644 --- a/src/tau_coding/extensions/runtime.py +++ b/src/tau_coding/extensions/runtime.py @@ -248,6 +248,7 @@ def __init__( self._renderer_failures_reported: set[str] = set() self._load_diagnostics: list[ResourceDiagnostic] = [] self._runtime_diagnostics: list[ResourceDiagnostic] = [] + self._paths: TauPaths = TauPaths() self._session: BoundSession | None = None self._ui: UiBridge = ui or NullUiBridge() self._turn_requested: TurnRequestedCallback | None = None @@ -266,6 +267,7 @@ def load( include_user_dir: bool = True, ) -> None: """Load built-ins, then discover extensions and run isolated setup.""" + self._paths = paths.paths or TauPaths(home=paths.root) self._load_built_ins() result = load_extensions( paths, @@ -851,6 +853,11 @@ def local_backend_registry(self) -> LocalBackendRegistry: """Return this staged runtime generation's local-backend registry.""" return self._local_backend_registry + @property + def paths(self) -> TauPaths: + """Return the resolved Tau filesystem paths for this runtime.""" + return self._paths + @property def extension_names(self) -> tuple[str, ...]: """Return visible extension names in load order.""" diff --git a/tests/test_extensions.py b/tests/test_extensions.py index 3b770efff..26723fd7e 100644 --- a/tests/test_extensions.py +++ b/tests/test_extensions.py @@ -1939,6 +1939,25 @@ async def executor( return AgentTool(name=name, label=name, description="d", parameters={}, execute_fn=executor) +def test_context_paths_exposes_resolved_paths_from_load(tmp_path: Path) -> None: + runtime = ExtensionRuntime() + resolved = TauPaths(home=tmp_path / "custom-home") + runtime.load(TauResourcePaths(root=tmp_path / "unused-root", paths=resolved)) + api = cast(ExtensionAPI, _register_inline_extension(runtime, "reader")) + runtime.bind(RecordingSession(tmp_path)) + + assert api.context.paths is resolved + + +def test_context_paths_falls_back_to_resource_root_home(tmp_path: Path) -> None: + runtime = ExtensionRuntime() + runtime.load(TauResourcePaths(root=tmp_path / "home-tau")) + api = cast(ExtensionAPI, _register_inline_extension(runtime, "reader")) + runtime.bind(RecordingSession(tmp_path)) + + assert api.context.paths.home == tmp_path / "home-tau" + + # -- coding-session integration --------------------------------------------------- @@ -2060,6 +2079,16 @@ async def test_session_lifecycle_recreates_built_in_in_a_fresh_generation( await session.aclose() +async def test_session_exposes_extension_paths_at_moved_home(tmp_path: Path) -> None: + await CodingSession.load( + _session_config(tmp_path, FakeProvider([]), extension_body=API_CAPTURING_EXTENSION) + ) + module = _loaded_extension_module("integration") + api = cast(ExtensionAPI, module.APIS[-1]) # type: ignore[attr-defined] + + assert api.context.paths.home == tmp_path / "home-tau" + + async def test_session_exposes_extension_tools_and_commands(tmp_path: Path) -> None: body = HELLO_TOOL_EXTENSION + ( "\n\ndef _cmd(args, context):\n" @@ -2871,6 +2900,8 @@ async def test_reset_for_reload_invalidates_prior_context_and_ui(tmp_path: Path) with pytest.raises(ExtensionError, match="stale after reload"): _ = context.cwd + with pytest.raises(ExtensionError, match="stale after reload"): + _ = context.paths with pytest.raises(ExtensionError, match="stale after reload"): _ = context.transcript with pytest.raises(ExtensionError, match="stale after reload"):