Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..agent_env_environment import OP_ADD_DATA, OP_GET_DATA, OP_RESET_DATA
from ..transfers import WriteNamespaceGrant
from ..types import METHOD_ADD, METHOD_RESET, FilePart, uploaded_object_path
from .app_runtime import paused_app
from .dataplane import data_files, file_parts, part_bytes

if TYPE_CHECKING:
Expand Down Expand Up @@ -131,7 +132,7 @@ def _served(self, op: str) -> Optional[Callable]:
if not self.paths:
return None
return {OP_ADD_DATA: functools.partial(add, paths=self.paths),
OP_GET_DATA: functools.partial(get, self.paths, self.runtime.paused),
OP_GET_DATA: functools.partial(get, self.paths),
OP_RESET_DATA: functools.partial(reset, self.paths)}[op]

async def _within(self, op: str, served: Callable, args: tuple, answer: asyncio.Future) -> Any:
Expand Down Expand Up @@ -177,13 +178,12 @@ async def add(parts: list, paths: list[str]) -> None:
await in_thread(write_within, Path.home(), files, paths)


async def get(paths: list[str], paused: Callable[[], contextlib.AbstractContextManager],
write_namespace: Optional[WriteNamespaceGrant]) -> list:
"""A part for each file under ``paths``, copied with the app ``paused``, so they are of one moment, and sent from
the copy once it runs again."""
async def get(paths: list[str], write_namespace: Optional[WriteNamespaceGrant]) -> list:
"""A part for each file under ``paths``, copied with the app this server launched paused (``paused_app``), so they
are of one moment, and sent from the copy once it runs again; an app's own data/get hook calls it so too."""
INITIAL.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=INITIAL.parent) as staging:
with paused():
with paused_app():
await in_thread(_stage, Path.home(), paths, Path(staging))
return await file_parts(Path(staging), paths, write_namespace)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,8 @@ def running(self) -> bool:
return _running() is not None

def paused(self) -> contextlib.AbstractContextManager:
"""The app this server launched last, every process of it, paused while the block runs, so a copy of its files
taken meanwhile is of one moment."""
pid = _running()
return session.paused(pid) if pid is not None else contextlib.nullcontext()
"""The app this server launched last paused while the block runs (``paused_app``)."""
return paused_app()

def exited(self) -> bool:
"""Log that the app this server launched last ended on its own, with its exit code or the signal that ended
Expand Down Expand Up @@ -242,6 +240,13 @@ def _running() -> Optional[int]:
return pid if pid is not None and session.alive(pid) else None


def paused_app() -> contextlib.AbstractContextManager:
"""The app this server launched last, every process of it, paused while the block runs, so a copy of its files
taken meanwhile is of one moment; nothing when it isn't running."""
pid = _running()
return session.paused(pid) if pid is not None else contextlib.nullcontext()


async def _bounded(name: str, check: Awaitable[Optional[str]]) -> Optional[str]:
"""What ``check`` finds wrong, within ``CHECK_TIMEOUT_S``; a check that runs past it or raises says so."""
try:
Expand Down
13 changes: 13 additions & 0 deletions packages/agentenv-protocol/tests/test_computer_app_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ async def test_an_app_with_only_paths_loads_exports_and_resets_them_as_installed
assert reset == {"Mail/inbox.txt": b"installed"}


@pytest.mark.asyncio
async def test_get_takes_paths_and_a_write_namespace_and_pauses_the_running_app_as_exported_apps_call_it(home,
desktop):
(home / "Mail").mkdir()
(home / "Mail/inbox.txt").write_text("kept")
await PathsMail()._computer_app.launch_app()

parts = await app_data.get(["Mail"], None)

assert [await part_bytes(part) for part in parts] == [b"kept"]
assert desktop == [("launch", 4242), ("pause", 4242), ("continue", 4242)]


@pytest.mark.asyncio
async def test_a_file_under_none_of_its_paths_or_through_a_link_out_of_them_is_refused_and_nothing_is_written(home):
(home / "Documents").mkdir()
Expand Down
Loading