The file /Users/agent/strukto/mirage/mirage/workspace/workspace.py has been updated successfully.

from mirage.types import FileStat, MountMode
from mirage.workspace.cache import LRUCache
from mirage.workspace.consistency import ConsistencyPolicy, is_fresh
from mirage.workspace.mount import MountRegistry
from mirage.workspace.sync import DirtyTracker
from mirage.workspace.types import Inode, SyncPolicy, SyncResult, TransferStats

class _PathLock:

from __future__ import annotations

import time

from mirage.backend.base import BaseBackend
from mirage.backend.filetype import guess_type
from pydantic import BaseModel

from mirage.types import FileStat, MountMode
from mirage.workspace.cache import LRUCache
from mirage.workspace.consistency import ConsistencyPolicy, is_fresh
from mirage.workspace.mount import MountRegistry
from mirage.workspace.sync import DirtyTracker, Inode, SyncPolicy, SyncResult

class TransferStats(BaseModel):
    bytes_read: int = 0
    bytes_written: int = 0
    read_ops: int = 0
    write_ops: int = 0
    cache_hits: int = 0
    cache_misses: int = 0
    total_read_ms: int = 0
    total_write_ms: int = 0

class _PathLock:

    def __init__(self, ws: Workspace, paths: list[str]) -> None:
        self._ws = ws
        self._paths = paths

    def __enter__(self) -> _PathLock:
        return self

    def __exit__(self, *_: object) -> None:
        self._ws.unlock_paths(self._paths)

class Workspace:
    """
    Unified virtual filesystem over heterogeneous backends.

    Mounts backends at virtual path prefixes and provides a command-level API
    (cat, ls, write, cp, mv, rm, ...) with transparent caching
    and write staging.

    Example::

        ws = Workspace(
            {"/data/": S3Backend(S3Config(bucket="my-bucket")),
             "/tmp/":  MemoryBackend()},
            cache_limit="256MB",
            sync_policy=SyncPolicy.STAGED,
        )
        content = ws.cat("/data/reports/q1.json")
        ws.write("/tmp/summary.txt", b"hello")
        ws.sync()
    """

    def __init__(
        self,
        backends: dict[str, BaseBackend | tuple],
        cache_limit: str | int = "512MB",
        sync_policy: SyncPolicy = SyncPolicy.STAGED,
        mode: MountMode = MountMode.READ,
        consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED,
        ttl_seconds: int = 300,
    ) -> None:
        self._registry = MountRegistry()
        self._cache = LRUCache(cache_limit)
        self._tracker = DirtyTracker()
        self._sync_policy = sync_policy
        self._ttl_seconds = ttl_seconds
        self._locked_paths: set[str] = set()
        self._transfer_stats: dict[str, TransferStats] = {}

        for prefix, value in backends.items():
            if isinstance(value, tuple) and len(value) == 3:
                backend, mount_mode, mount_consistency = value
            elif isinstance(value, tuple):
                backend, mount_mode = value
                mount_consistency = consistency
            else:
                backend = value
                mount_mode = mode
                mount_consistency = consistency
            self._registry.mount(prefix, backend, mount_mode,
                                 mount_consistency)

    # ── mount management ────────────────────────────────────────────────────

    def mount(
            self,
            prefix: str,
            backend: BaseBackend,
            mode: MountMode = MountMode.READ,
            consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED) -> None:
        self._registry.mount(prefix, backend, mode, consistency)

    def backend(self, path: str) -> BaseBackend:
        backend, _, _ = self._registry.resolve(path)
        return backend

    # ── read ops ────────────────────────────────────────────────────────────

    def cat(self, path: str) -> bytes:
        inode = self._tracker.get(path)
        if inode and inode.dirty:
            entry = self._cache.get(path)
            if entry is not None:
                self._record_transfer(path,
                                      "read",
                                      len(entry.data),
                                      0,
                                      cache_hit=True)
                return entry.data

        entry = self._cache.get(path)
        remote_stat = None
        if entry is not None:
            consistency = self._registry.mount_for(path).consistency
            backend, bp, _ = self._registry.resolve(path)
            fresh, remote_stat = is_fresh(entry, consistency,
                                          self._ttl_seconds, backend, bp)
            if fresh:
                self._record_transfer(path,
                                      "read",
                                      len(entry.data),
                                      0,
                                      cache_hit=True)
                return entry.data
        else:
            backend, bp, _ = self._registry.resolve(path)

        t0 = time.monotonic()
        data = backend.cat(bp)
        duration_ms = int((time.monotonic() - t0) * 1000)
        self._record_transfer(path,
                              "read",
                              len(data),
                              duration_ms,
                              cache_hit=False)
        if remote_stat is None:
            remote_stat = backend.stat(bp)
        self._cache.put(path,
                        data,
                        etag=remote_stat.extra.get("etag"),
                        modified=remote_stat.modified,
                        size=remote_stat.size)
        self._tracker.on_read(path, data, self._mount_prefix(path))
        return data

    def ls(self, path: str) -> list[str]:
        backend, bp, _ = self._registry.resolve(path)
        prefix = self._mount_prefix(path).rstrip("/")
        backend_ok = False
        try:
            backend_entries = backend.ls(bp)
            backend_ok = True
        except FileNotFoundError:
            backend_entries = []
        result: set[str] = {
            prefix + "/" + e.lstrip("/")
            for e in backend_entries
        }
        norm_path = "/" + path.strip("/")
        dir_prefix = norm_path.rstrip("/") + "/"
        has_staged_children = False
        for inode in self._tracker.dirty():
            vp = inode.virtual_path
            if not vp.startswith(dir_prefix):
                continue
            if inode.checksum is None:
                result.discard(vp)
                continue
            has_staged_children = True
            remainder = vp[len(dir_prefix):]
            first = remainder.split("/")[0]
            if not first:
                continue
            if "/" in remainder:
                result.add(dir_prefix + first + "/")
            else:
                result.add(dir_prefix + first)
        if not backend_ok and not has_staged_children:
            raise FileNotFoundError(path)
        return sorted(result)

    def ls_l(self, path: str) -> list[FileStat]:
        backend, bp, _ = self._registry.resolve(path)
        return backend.ls_l(bp)

    def stat(self, path: str) -> FileStat:
        inode = self._tracker.get(path)
        if inode and inode.dirty and inode.checksum is not None:
            entry = self._cache.get(path)
            size = len(entry.data) if entry is not None else inode.size
            return FileStat(
                name=path.rstrip("/").rsplit("/", 1)[-1],
                size=size,
                type=guess_type(path),
            )
        backend, bp, _ = self._registry.resolve(path)
        return backend.stat(bp)

    def exists(self, path: str) -> bool:
        try:
            self.stat(path)
            return True
        except (FileNotFoundError, ValueError):
            return False

    def head(self, path: str, lines: int = 10) -> bytes:
        backend, bp, _ = self._registry.resolve(path)
        return backend.head(bp, lines)

    def tail(self, path: str, lines: int = 10) -> bytes:
        backend, bp, _ = self._registry.resolve(path)
        return backend.tail(bp, lines)

    def read(self, path: str, offset: int, size: int) -> bytes:
        inode = self._tracker.get(path)
        if inode and inode.dirty:
            entry = self._cache.get(path)
            if entry is not None:
                return entry.data[offset:offset + size]

        entry = self._cache.get(path)
        remote_stat = None
        if entry is not None:
            consistency = self._registry.mount_for(path).consistency
            backend, bp, _ = self._registry.resolve(path)
            fresh, remote_stat = is_fresh(entry, consistency,
                                          self._ttl_seconds, backend, bp)
            if fresh:
                return entry.data[offset:offset + size]
        else:
            backend, bp, _ = self._registry.resolve(path)

        data = backend.cat(bp)
        if remote_stat is None:
            remote_stat = backend.stat(bp)
        self._cache.put(path,
                        data,
                        etag=remote_stat.extra.get("etag"),
                        modified=remote_stat.modified,
                        size=remote_stat.size)
        self._tracker.on_read(path, data, self._mount_prefix(path))
        return data[offset:offset + size]

    def grep(self, pattern: str, path: str) -> list[str]:
        backend, bp, _ = self._registry.resolve(path)
        return backend.grep(pattern, bp)

    # ── write ops ───────────────────────────────────────────────────────────

    def write(self, path: str, data: bytes) -> None:
        self._assert_not_locked(path)
        self._assert_writable(path)
        backend, bp, _ = self._registry.resolve(path)
        self._cache.put(path, data)
        self._tracker.on_write(path, data, self._mount_prefix(path))
        self._record_transfer(path, "write", len(data), 0)
        if self._sync_policy == SyncPolicy.IMMEDIATE:
            backend.put(bp, data)
            self._tracker.on_read(path, data, self._mount_prefix(path))

    def mkdir(self, path: str) -> None:
        self._assert_writable(path)
        backend, bp, _ = self._registry.resolve(path)
        backend.mkdir(bp)

    def rm(self, path: str) -> None:
        self._assert_writable(path)
        backend, bp, _ = self._registry.resolve(path)
        inode = self._tracker.get(path)
        staged_only = inode and inode.dirty and inode.remote_checksum is None
        if not staged_only:
            backend.unlink(bp)
        self._cache.invalidate(path)
        self._tracker.on_delete(path)

    def mv(self, src: str, dst: str) -> None:
        self._assert_writable(src)
        sb, sbp, _ = self._registry.resolve(src)
        db, dbp, _ = self._registry.resolve(dst)
        src_inode = self._tracker.get(src)
        staged_only = (src_inode and src_inode.dirty
                       and src_inode.remote_checksum is None)
        if staged_only:
            entry = self._cache.get(src)
            data = entry.data if entry else b""
            self._cache.put(dst, data)
            self._tracker.on_write(dst, data, self._mount_prefix(dst))
            self._cache.invalidate(src)
            self._tracker.on_delete(src)
        elif sb is db:
            sb.rename(sbp, dbp)
            self._cache.invalidate(src)
        else:
            data = sb.cat(sbp)
            db.put(dbp, data)
            sb.unlink(sbp)
            self._cache.invalidate(src)

    def cp(self, src: str, dst: str) -> None:
        self._assert_writable(dst)
        data = self.cat(src)
        db, dbp, _ = self._registry.resolve(dst)
        db.put(dbp, data)

    # ── sync lifecycle ──────────────────────────────────────────────────────

    def sync(self, path: str | None = None) -> list[SyncResult]:
        results: list[SyncResult] = []
        targets = [
            i for i in self._tracker.dirty()
            if path is None or i.virtual_path == path
        ]
        for inode in targets:
            backend, bp, _ = self._registry.resolve(inode.virtual_path)
            entry = self._cache.get(inode.virtual_path)
            data = entry.data if entry else None
            result = self._tracker.on_sync(inode.virtual_path, bp, data,
                                           backend)
            results.append(result)
        return results

    def status(self) -> list[Inode]:
        return self._tracker.status()

    def reset(self) -> None:
        self._tracker.reset()
        self._cache.clear()

    # ── path locking ─────────────────────────────────────────────────────────

    def _lock_path(self, path: str) -> None:
        self._locked_paths.add(path)

    def unlock_paths(self, paths: list[str]) -> None:
        for p in paths:
            self._locked_paths.discard(p)

    def lock_paths(self, paths: list[str]) -> _PathLock:
        for p in paths:
            self._lock_path(p)
        return _PathLock(self, paths)

    def _assert_not_locked(self, path: str) -> None:
        if path in self._locked_paths:
            raise PermissionError(
                f"path {path!r} is locked by remote execution")

    # ── helpers ─────────────────────────────────────────────────────────────

    def _mount_prefix(self, path: str) -> str:
        norm = "/" + path.strip("/")
        for m in self._registry.mounts():
            if norm == m.prefix.rstrip("/") or norm.startswith(m.prefix):
                return m.prefix
        return "/"

    def _assert_writable(self, path: str) -> None:
        _, _, mode = self._registry.resolve(path)
        if mode == MountMode.READ:
            raise PermissionError(f"mount at {path!r} is read-only")

    def _record_transfer(self,
                         path: str,
                         op: str,
                         nbytes: int,
                         duration_ms: int,
                         cache_hit: bool | None = None) -> None:
        prefix = self._mount_prefix(path)
        if prefix not in self._transfer_stats:
            self._transfer_stats[prefix] = TransferStats()
        stats = self._transfer_stats[prefix]
        if op == "read":
            stats.bytes_read += nbytes
            stats.read_ops += 1
            stats.total_read_ms += duration_ms
            if cache_hit is True:
                stats.cache_hits += 1
            elif cache_hit is False:
                stats.cache_misses += 1
        elif op == "write":
            stats.bytes_written += nbytes
            stats.write_ops += 1
            stats.total_write_ms += duration_ms

-from mirage.workspace.sync import DirtyTracker, Inode, SyncPolicy, SyncResult

+from mirage.workspace.sync import DirtyTracker

+from mirage.workspace.types import Inode, SyncPolicy, SyncResult, TransferStats


TodoWrite

Create workspace/types.py

Update sync.py to import from workspace/types.py

Updating sync.py to import from workspace/types.py

Update workspace.py to import from workspace/types.py

Updating workspace.py to import from workspace/types.py

Update all other import sites

Verify tests pass


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Create workspace/types.py

Update sync.py to import from workspace/types.py

Updating sync.py to import from workspace/types.py

Update workspace.py to import from workspace/types.py

Updating workspace.py to import from workspace/types.py

Update all other import sites

Verify tests pass

Create workspace/types.py

Update all other import sites

Verify tests pass


Now update `workspace/__init__.py` to re-export from `workspace/types`:


     1→from mirage.workspace.sync import Inode, SyncPolicy, SyncResult
     2→from mirage.workspace.workspace import Workspace
     3→
     4→__all__ = ["Workspace", "SyncPolicy", "Inode", "SyncResult"]
     5→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

from mirage.workspace.sync import Inode, SyncPolicy, SyncResult
from mirage.workspace.workspace import Workspace

__all__ = ["Workspace", "SyncPolicy", "Inode", "SyncResult"]


Edit

from mirage.workspace.sync import Inode, SyncPolicy, SyncResult
from mirage.workspace.workspace import Workspace

from mirage.workspace.types import Inode, SyncPolicy, SyncResult
from mirage.workspace.workspace import Workspace


The file /Users/agent/strukto/mirage/mirage/workspace/__init__.py has been updated successfully.

from mirage.workspace.types import Inode, SyncPolicy, SyncResult
from mirage.workspace.workspace import Workspace

from mirage.workspace.sync import Inode, SyncPolicy, SyncResult
from mirage.workspace.workspace import Workspace

__all__ = ["Workspace", "SyncPolicy", "Inode", "SyncResult"]

-from mirage.workspace.sync import Inode, SyncPolicy, SyncResult

+from mirage.workspace.types import Inode, SyncPolicy, SyncResult


Now check for any other import sites that import from `workspace.sync` or `workspace.workspace` for these types:


tests/test_types.py:8:from mirage.workspace.sync import Inode, SyncResult
tests/test_types.py:9:from mirage.workspace.workspace import TransferStats
mirage/server/fastapi/config.py:4:from mirage.workspace.sync import SyncPolicy
mirage/server/service/local.py:21:from mirage.workspace.sync import SyncPolicy


Update these 4 import sites:


     1→from pydantic import BaseModel
     2→
     3→from mirage.types import MountMode
     4→from mirage.workspace.sync import SyncPolicy
     5→from mirage.workspace.consistency import ConsistencyPolicy
     6→
     7→
     8→class MountConfig(BaseModel):
     9→    uri: str | None = None
    10→    backend: str | None = None

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

from pydantic import BaseModel

from mirage.types import MountMode
from mirage.workspace.sync import SyncPolicy
from mirage.workspace.consistency import ConsistencyPolicy

class MountConfig(BaseModel):
    uri: str | None = None
    backend: str | None = None


     1→import time
     2→import uuid
     3→from datetime import datetime, timezone
     4→
     5→from mirage.backend.loader import load_backend_class
     6→from mirage.cli.config import parse_mount_uri, split_mount_spec
     7→from mirage.commands import COMMANDS, _resolve
     8→from mirage.commands.builtin import nav
     9→from mirage.commands.custom.registry import CommandRegistry, ExternalCommand
    10→from mirage.fuse import FUSE_OPS
    11→from mirage.fuse.loader import build_registry_from_config, load_fuse_op_class
    12→from mirage.observe.entries import (CommandEntry, CommandResult, DispatchInfo,
    13→                                    ParsedCommand, SessionEntry, WorkspaceInfo)
    14→from mirage.observe.session_log import get_or_create_log
    15→from mirage.runtime.base import _BLOCKED, CommandNotAllowedError
    16→from mirage.server.service.base import MirageService
    17→from mirage.server.service.cache import FileCache
    18→from mirage.server.service.session import WorkspaceSession, _unmount_fuse
    19→from mirage.types import (FileStat, MountMode, RuntimeType)
    20→from mirage.workspace import Workspace
    21→from mirage.workspace.sync import SyncPolicy
    22→from mirage.workspace.consistency import ConsistencyPolicy
    23→
    24→
    25→class LocalService(MirageService):

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import time
import uuid
from datetime import datetime, timezone

from mirage.backend.loader import load_backend_class
from mirage.cli.config import parse_mount_uri, split_mount_spec
from mirage.commands import COMMANDS, _resolve
from mirage.commands.builtin import nav
from mirage.commands.custom.registry import CommandRegistry, ExternalCommand
from mirage.fuse import FUSE_OPS
from mirage.fuse.loader import build_registry_from_config, load_fuse_op_class
from mirage.observe.entries import (CommandEntry, CommandResult, DispatchInfo,
                                    ParsedCommand, SessionEntry, WorkspaceInfo)
from mirage.observe.session_log import get_or_create_log
from mirage.runtime.base import _BLOCKED, CommandNotAllowedError
from mirage.server.service.base import MirageService
from mirage.server.service.cache import FileCache
from mirage.server.service.session import WorkspaceSession, _unmount_fuse
from mirage.types import (FileStat, MountMode, RuntimeType)
from mirage.workspace import Workspace
from mirage.workspace.sync import SyncPolicy
from mirage.workspace.consistency import ConsistencyPolicy

class LocalService(MirageService):


from mirage.workspace.sync import SyncPolicy

from mirage.workspace.types import SyncPolicy


The file /Users/agent/strukto/mirage/mirage/server/fastapi/config.py has been updated successfully.

from pydantic import BaseModel

from mirage.types import MountMode
from mirage.workspace.sync import SyncPolicy
from mirage.workspace.consistency import ConsistencyPolicy

class MountConfig(BaseModel):
    uri: str | None = None
    backend: str | None = None
    args: dict[str, str] | None = None
    mode: MountMode | None = None
    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED

class CreateWorkspaceRequest(BaseModel):
    mounts: dict[str, str | MountConfig]
    sync_policy: SyncPolicy = SyncPolicy.STAGED
    mode: MountMode = MountMode.WRITE
    consistency: ConsistencyPolicy = ConsistencyPolicy.CACHED
    ttl_seconds: int = 300
    runtime: str | None = None
    fuse_ops: dict[str, str] | None = None

class RegisterFuseRequest(BaseModel):
    mountpoint: str
    pid: int
    mount_specs: list[str] = []
    runtime: str | None = None

class FilePayloadRequest(BaseModel):
    checksum: str
    data: str | None = None

class ExecRequest(BaseModel):
    command: str
    cwd: str | None = None
    stdin: str | None = None
    input_files: dict[str, FilePayloadRequest] | None = None

class FuseReportRequest(BaseModel):
    mountpoint: str
    status: dict
    transfers: dict = {}
    ops: list[dict] = []

class RegisterCommandRequest(BaseModel):
    name: str
    exec: str
    description: str = ""

class RegisterFuseOpRequest(BaseModel):
    pattern: str
    handler: str

class ReadRequest(BaseModel):
    path: str
    offset: int
    size: int

class WriteRequest(BaseModel):
    path: str
    offset: int
    data: str

class PathRequest(BaseModel):
    path: str

class ReaddirRequest(BaseModel):
    path: str
    cursor: str | None = None

class RenameRequest(BaseModel):
    src: str
    dst: str

class TruncateRequest(BaseModel):
    path: str
    size: int

-from mirage.workspace.sync import SyncPolicy

+from mirage.workspace.types import SyncPolicy


from mirage.workspace.sync import SyncPolicy

from mirage.workspace.types import SyncPolicy


The file /Users/agent/strukto/mirage/mirage/server/service/local.py has been updated successfully.

import time
import uuid
from datetime import datetime, timezone

from mirage.backend.loader import load_backend_class
from mirage.cli.config import parse_mount_uri, split_mount_spec
from mirage.commands import COMMANDS, _resolve
from mirage.commands.builtin import nav
from mirage.commands.custom.registry import CommandRegistry, ExternalCommand
from mirage.fuse import FUSE_OPS
from mirage.fuse.loader import build_registry_from_config, load_fuse_op_class
from mirage.observe.entries import (CommandEntry, CommandResult, DispatchInfo,
                                    ParsedCommand, SessionEntry, WorkspaceInfo)
from mirage.observe.session_log import get_or_create_log
from mirage.runtime.base import _BLOCKED, CommandNotAllowedError
from mirage.server.service.base import MirageService
from mirage.server.service.cache import FileCache
from mirage.server.service.session import WorkspaceSession, _unmount_fuse
from mirage.types import (FileStat, MountMode, RuntimeType)
from mirage.workspace import Workspace
from mirage.workspace.sync import SyncPolicy
from mirage.workspace.consistency import ConsistencyPolicy

class LocalService(MirageService):

    def __init__(self) -> None:
        self._sessions: dict[str, WorkspaceSession] = {}
        self._file_cache = FileCache()

    def _get_session(self, ws_id: str) -> WorkspaceSession:
        session = self._sessions.get(ws_id)
        if session is None:
            raise KeyError(f"workspace {ws_id!r} not found")
        if session.fuse_mounted:
            raise ValueError("not supported for fuse workspaces")
        return session

    def create_workspace(self, config: dict) -> str:
        mode = config.get("mode", "write")
        consistency = config.get("consistency", "cached")
        default_mode = MountMode(mode)
        default_consistency = ConsistencyPolicy(consistency)
        mounts = config.get("mounts", {})

        backends: dict = {}
        for prefix, value in mounts.items():
            if isinstance(value, str):
                backends[prefix] = parse_mount_uri(value)
            else:
                backend_spec = value.get("backend")
                if backend_spec:
                    cls = load_backend_class(backend_spec)
                    backend = cls(**(value.get("args") or {}))
                else:
                    backend = parse_mount_uri(value.get("uri") or "")
                mount_mode = (MountMode(value["mode"])
                              if value.get("mode") else default_mode)
                mount_consistency = (ConsistencyPolicy(value["consistency"])
                                     if value.get("consistency") else
                                     default_consistency)
                if (mount_mode != default_mode
                        or mount_consistency != default_consistency):
                    backends[prefix] = (backend, mount_mode, mount_consistency)
                else:
                    backends[prefix] = backend

        ws = Workspace(
            backends,
            sync_policy=SyncPolicy(config.get("sync_policy", "staged")),
            mode=default_mode,
            consistency=default_consistency,
            ttl_seconds=config.get("ttl_seconds", 300),
        )

        mounts_info: list[dict] = []
        for prefix, value in mounts.items():
            if isinstance(value, str):
                uri = value
            else:
                uri = value.get("uri") or ""
                if not uri and value.get("backend"):
                    uri = f"{value['backend']}://"
            mounts_info.append({"prefix": prefix, "uri": uri})

        ws_id = str(uuid.uuid4())[:8]
        registry = CommandRegistry()
        fuse_ops_config = config.get("fuse_ops") or {}
        fuse_registry = build_registry_from_config(fuse_ops_config)
        runtime = config.get("runtime")
        rt_type = RuntimeType(runtime) if runtime else None

        self._sessions[ws_id] = WorkspaceSession(
            id=ws_id,
            ws=ws,
            registry=registry,
            mode=mode,
            mounts=mounts_info,
            runtime_type=rt_type,
            fuse_registry=fuse_registry,
        )

        log = get_or_create_log(ws_id)
        mount_info = {}
        for m in ws._registry.mounts():
            mount_info[m.prefix] = {
                "type": type(m.backend).__name__,
                "mode": m.mode.value,
            }
        session_entry = SessionEntry(
            timestamp=datetime.now(timezone.utc).isoformat(),
            workspace=WorkspaceInfo(
                mounts=mount_info,
                cache_limit=str(ws._cache.limit),
                runtime_type=rt_type.value if rt_type else None,
            ),
            commands=registry.list_commands(),
        )
        log.write_session(session_entry)
        return ws_id

    def delete_workspace(self, ws_id: str) -> None:
        session = self._sessions.get(ws_id)
        if session is None:
            raise KeyError(f"workspace {ws_id!r} not found")
        if session.fuse_mounted and session.mountpoint:
            _unmount_fuse(session.mountpoint, session.pid)
        self._file_cache.clear(ws_id)
        del self._sessions[ws_id]

    def list_workspaces(self) -> list[dict]:
        return [{
            "id": s.id,
            "fuse_mounted": s.fuse_mounted,
            "mode": s.mode,
            "mountpoint": s.mountpoint,
            "mounts": s.mounts,
            "runtime": s.runtime_type.value if s.runtime_type else None,
        } for s in self._sessions.values()]

    def exec(
        self,
        ws_id: str,
        command: str,
        cwd: str | None = None,
        stdin: bytes | None = None,
        input_files: dict[str, tuple[str, bytes | None]] | None = None,
    ) -> tuple[bool, str, dict[str, bytes] | None, dict[str, str] | None]:
        session = self._get_session(ws_id)
        if cwd is not None:
            session.cwd = cwd

        cache_status: dict[str, str] = {}
        if input_files:
            for path, (checksum, data) in input_files.items():
                hit, _ = self._file_cache.get(ws_id, path, checksum)
                if hit:
                    cache_status[path] = "hit"
                elif data is not None:
                    self._file_cache.put(ws_id, path, checksum, data)
                    session.ws.write(path, data)
                    cache_status[path] = "miss"
                else:
                    return (False, f"cache miss: {path}", None, None)

        cmd_name = command.strip().split()[0] if command.strip() else ""
        session.command_stats.total_commands += 1
        session.command_stats.command_counts[cmd_name] = (
            session.command_stats.command_counts.get(cmd_name, 0) + 1)
        session.command_stats.last_command = command
        session.command_stats.last_timestamp = datetime.now(
            timezone.utc).isoformat()
        t0 = time.monotonic()
        try:
            if session.runtime_type is not None and cmd_name in _BLOCKED:
                raise CommandNotAllowedError(
                    f"{cmd_name!r} is not allowed in this runtime")
            output = session.registry.run(session.ws,
                                          command,
                                          cwd=session.cwd,
                                          stdin=stdin)
            duration_ms = int((time.monotonic() - t0) * 1000)
            if command.strip().startswith("cd "):
                parts = command.strip().split(None, 1)
                if len(parts) > 1:
                    session.cwd = nav.cd(session.ws, session.cwd,
                                         _resolve(session.cwd, parts[1]))
            args = command.strip().split()[1:] if command.strip() else []
            source = "builtin" if cmd_name in COMMANDS else "external"
            log = get_or_create_log(ws_id)
            entry = CommandEntry(
                timestamp=datetime.now(timezone.utc).isoformat(),
                raw=command,
                parsed=ParsedCommand(
                    pipeline=[command],
                    args={cmd_name: args},
                ),
                dispatch=[
                    DispatchInfo(
                        command=cmd_name,
                        handler=f"_{cmd_name}"
                        if source == "builtin" else cmd_name,
                        source=source,
                    )
                ],
                result=CommandResult(
                    exit_code=0,
                    stdout_bytes=len(output.encode()) if output else 0,
                    duration_ms=duration_ms,
                ),
            )
            log.write_command(entry)

            return (True, output, None, cache_status or None)
        except Exception as e:
            duration_ms = int((time.monotonic() - t0) * 1000)
            session.command_stats.total_errors += 1
            log = get_or_create_log(ws_id)
            entry = CommandEntry(
                timestamp=datetime.now(timezone.utc).isoformat(),
                raw=command,
                result=CommandResult(exit_code=1,
                                     stdout_bytes=0,
                                     duration_ms=duration_ms),
            )
            log.write_command(entry)
            return (False, str(e), None, None)

    def list_commands(self, ws_id: str) -> list[str]:
        session = self._get_session(ws_id)
        return session.registry.list_commands()

    def register_command(self,
                         ws_id: str,
                         name: str,
                         exec_str: str,
                         description: str = "") -> None:
        session = self._get_session(ws_id)
        session.registry.register(
            ExternalCommand(name=name, exec=exec_str, description=description))

    def sync(self, ws_id: str) -> list[dict]:
        session = self._get_session(ws_id)
        results = session.ws.sync()
        return [{"path": r.path, "status": r.status} for r in results]

    def status(self, ws_id: str) -> list[dict]:
        session = self._get_session(ws_id)
        inodes = session.ws.status()
        return [{
            "path": i.virtual_path,
            "dirty": i.dirty,
            "size": i.size
        } for i in inodes]

    # ── File I/O (backend-level) ─────────────────────────────────────────

    def io_read(self, ws_id: str, path: str, offset: int, size: int) -> bytes:
        session = self._get_session(ws_id)
        return session.ws.read(path, offset, size)

    def io_write(self, ws_id: str, path: str, offset: int, data: bytes) -> int:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        return backend.write(backend_path, offset, data)

    def io_stat(self, ws_id: str, path: str) -> FileStat:
        session = self._get_session(ws_id)
        return session.ws.stat(path)

    def io_readdir(self,
                   ws_id: str,
                   path: str,
                   cursor: str | None = None) -> tuple[list[str], str | None]:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        return backend.readdir(backend_path, cursor)

    def io_create(self, ws_id: str, path: str) -> None:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        backend.create(backend_path)

    def io_unlink(self, ws_id: str, path: str) -> None:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        backend.unlink(backend_path)

    def io_mkdir(self, ws_id: str, path: str) -> None:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        backend.mkdir(backend_path)

    def io_rmdir(self, ws_id: str, path: str) -> None:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        backend.rmdir(backend_path)

    def io_rename(self, ws_id: str, src: str, dst: str) -> None:
        session = self._get_session(ws_id)
        backend, src_path, _ = session.ws._registry.resolve(src)
        _, dst_path, _ = session.ws._registry.resolve(dst)
        backend.rename(src_path, dst_path)

    def io_truncate(self, ws_id: str, path: str, size: int) -> None:
        session = self._get_session(ws_id)
        backend, backend_path, _ = session.ws._registry.resolve(path)
        backend.truncate(backend_path, size)

    def io_fuse_cat(self, ws_id: str, path: str) -> bytes:
        session = self._get_session(ws_id)
        st = session.ws.stat(path)
        op = session.fuse_registry.resolve(path, st.size or 0, session.ws)
        ctx = op.on_open(session.ws, path, st.size or 0)
        return op.read(session.ws, path, 0, st.size or 0, ctx)

    # ── FuseOp management ────────────────────────────────────────────────

    def list_fuse_ops(self, ws_id: str) -> list[dict]:
        session = self._get_session(ws_id)
        return session.fuse_registry.list_ops()

    def register_fuse_op(self, ws_id: str, pattern: str, handler: str) -> None:
        session = self._get_session(ws_id)
        op = FUSE_OPS.get(handler)
        if op is None:
            try:
                cls = load_fuse_op_class(handler)
                op = cls()
            except (ValueError, Exception) as e:
                raise ValueError(f"unknown handler: {handler} ({e})")
        session.fuse_registry.register(pattern, op)

    # ── FUSE-specific (not part of MirageService ABC) ────────────────────

    def register_fuse(self,
                      mountpoint: str,
                      pid: int,
                      mount_specs: list[str] | None = None,
                      runtime: str | None = None) -> str:
        fuse_mounts: list[dict] = []
        for spec in (mount_specs or []):
            try:
                uri, prefix = split_mount_spec(spec)
                fuse_mounts.append({"prefix": prefix, "uri": uri})
            except ValueError:
                pass
        ws_id = str(uuid.uuid4())[:8]
        rt_type = RuntimeType(runtime) if runtime else None
        self._sessions[ws_id] = WorkspaceSession(
            id=ws_id,
            ws=None,
            registry=None,
            fuse_mounted=True,
            mountpoint=mountpoint,
            pid=pid,
            mounts=fuse_mounts,
            runtime_type=rt_type,
        )
        mount_info = {m["prefix"]: {"type": m["uri"]} for m in fuse_mounts}
        log = get_or_create_log(ws_id)
        session_entry = SessionEntry(
            timestamp=datetime.now(timezone.utc).isoformat(),
            workspace=WorkspaceInfo(
                mounts=mount_info,
                runtime_type=rt_type.value if rt_type else None,
            ),
        )
        log.write_session(session_entry)
        return ws_id

    def report_fuse_stats(self,
                          mountpoint: str,
                          status: dict,
                          transfers: dict | None = None,
                          ops: list[dict] | None = None) -> bool:
        for s in self._sessions.values():
            if s.fuse_mounted and s.mountpoint == mountpoint:
                s.reported_status = status
                s.reported_transfers = transfers or {}
                if ops:
                    log = get_or_create_log(s.id)
                    for op in ops:
                        log._append(op)
                return True
        return False


Now update `tests/test_types.py`:


from mirage.workspace.sync import Inode, SyncResult
from mirage.workspace.workspace import TransferStats

from mirage.workspace.types import Inode, SyncResult, TransferStats


The file /Users/agent/strukto/mirage/tests/test_types.py has been updated successfully.

import pytest
from pydantic import ValidationError

from mirage.cli.config import BackendType
from mirage.commands.types import CommandResult, FilePayload, RemoteCommandOptions
from mirage.server.service.session import CommandStats
from mirage.types import FileStat
from mirage.workspace.sync import Inode, SyncResult
from mirage.workspace.workspace import TransferStats

def test_filestat_defaults():
    fs = FileStat(name="foo.txt")
    assert fs.name == "foo.txt"
    assert fs.size is None
    assert fs.extra == {}

def test_filestat_immutable():
    fs = FileStat(name="foo.txt")
    with pytest.raises(ValidationError):
        fs.name = "bar.txt"

def test_inode_defaults():
    inode = Inode(virtual_path="/a/b.txt", backend_mount="/a/")
    assert inode.dirty is False
    assert inode.checksum is None

def test_inode_mutable():
    inode = Inode(virtual_path="/a/b.txt", backend_mount="/a/")
    inode.dirty = True
    assert inode.dirty is True

def test_syncresult_fields():
    r = SyncResult(path="/a/b.txt", op="create", status="ok")
    assert r.op == "create"
    assert r.duration_ms == 0

def test_syncresult_immutable():
    r = SyncResult(path="/a/b.txt", op="create", status="ok")
    with pytest.raises(ValidationError):
        r.status = "error"

def test_transferstats_mutable():
    ts = TransferStats()
    ts.bytes_read += 100
    assert ts.bytes_read == 100

def test_commandstats_defaults():
    cs = CommandStats()
    assert cs.total_commands == 0
    assert cs.command_counts == {}

def test_command_result_defaults():
    r = CommandResult(stdout="hello")
    assert r.stdout == "hello"
    assert r.output_files == {}

def test_command_result_with_files():
    r = CommandResult(stdout="done", output_files={"/out.csv": b"a,b"})
    assert r.output_files["/out.csv"] == b"a,b"

def test_file_payload_with_data():
    p = FilePayload(checksum="abc123", data=b"hello")
    assert p.checksum == "abc123"
    assert p.data == b"hello"

def test_file_payload_checksum_only():
    p = FilePayload(checksum="abc123")
    assert p.data is None

def test_remote_command_options_defaults():
    opts = RemoteCommandOptions()
    assert opts.max_file_size == "100MB"
    assert opts.cache == "enabled"

def test_remote_command_options_custom():
    opts = RemoteCommandOptions(max_file_size="500MB", cache="disabled")
    assert opts.max_file_size == "500MB"
    assert opts.cache == "disabled"

def test_remote_command_options_only_two_fields():
    opts = RemoteCommandOptions()
    assert opts.max_file_size == "100MB"
    assert opts.cache == "enabled"
    assert set(RemoteCommandOptions.model_fields.keys()) == {
        "max_file_size", "cache"
    }

def test_backend_type_values():
    assert BackendType.MEMORY == "memory"
    assert BackendType.LOCAL == "local"
    assert BackendType.S3 == "s3"
    assert BackendType.R2 == "r2"
    assert BackendType.HTTP == "http"
    assert BackendType.HTTPS == "https"

def test_backend_type_string_equality():
    assert BackendType.MEMORY.value == "memory"
    assert BackendType.LOCAL.value == "local"
    assert BackendType.S3.value == "s3"
    assert BackendType.R2.value == "r2"
    assert BackendType.HTTP.value == "http"
    assert BackendType.HTTPS.value == "https"
    assert BackendType("memory") is BackendType.MEMORY
    assert BackendType("s3") is BackendType.S3

def test_backend_type_members():
    assert set(BackendType) == {
        BackendType.MEMORY,
        BackendType.LOCAL,
        BackendType.S3,
        BackendType.R2,
        BackendType.HTTP,
        BackendType.HTTPS,
    }

-from mirage.workspace.sync import Inode, SyncResult

-from mirage.workspace.workspace import TransferStats

+from mirage.workspace.types import Inode, SyncResult, TransferStats


Now check for any other files that might import `SyncPolicy` from `workspace.sync` directly (not going through `workspace.__init__`):


tests/workspace/test_sync.py:4:from mirage.workspace.sync import DirtyTracker
mirage/workspace/workspace.py:11:from mirage.workspace.sync import DirtyTracker


Good — only `DirtyTracker` imports remain from `workspace.sync`, which is correct. Now verify tests pass:


TodoWrite

Create workspace/types.py

Update sync.py to import from workspace/types.py

Updating sync.py to import from workspace/types.py

Update workspace.py to import from workspace/types.py

Updating workspace.py to import from workspace/types.py

Update all other import sites

Verify tests pass


Create workspace/types.py

Update all other import sites

Verify tests pass

Create workspace/types.py

Update all other import sites

Verify tests pass


Bash

uv run pytest --no-cov -q 2>&1 | tail -5

Run tests to verify refactoring


........................................................................ [ 65%]
........................................................................ [ 76%]
........................................................................ [ 87%]
........................................................................ [ 98%]
.........                                                                [100%]


uv run pytest --no-cov -q 2>&1 | grep -E "passed|failed|error"

Get test summary


uv run pytest --no-cov -q 2>&1 | tail -2

Get final test count


........................................................................ [ 98%]
.........                                                                [100%]


Get test result summary


........................................................................ [ 98%]
.........                                                                [100%]
656 passed, 1 skipped in 2.17s


All 656 tests pass.


TodoWrite

Create workspace/types.py

Update sync.py to import from workspace/types.py

Updating sync.py to import from workspace/types.py

Update workspace.py to import from workspace/types.py

Updating workspace.py to import from workspace/types.py

Update all other import sites

Verify tests pass


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Create workspace/types.py

Update sync.py to import from workspace/types.py

Update workspace.py to import from workspace/types.py

Update all other import sites

Verify tests pass

Create workspace/types.py

Update all other import sites

Verify tests pass


Done. The split is complete:

- [workspace/types.py](mirage/workspace/types.py) — data models only: `SyncPolicy`, `Inode`, `SyncResult`, `TransferStats`
- [workspace/sync.py](mirage/workspace/sync.py) — engine only: `DirtyTracker`, `_md5`
- [workspace/workspace.py](mirage/workspace/workspace.py) — imports types from `workspace/types`
- [workspace/__init__.py](mirage/workspace/__init__.py) — re-exports from `workspace/types`
- Updated 4 import sites (`server/fastapi/config.py`, `server/service/local.py`, `tests/test_types.py`)


tests/cli/test_app.py:51:80: E501 line too long (81 > 79 characters)
tests/cli/test_app.py:63:80: E501 line too long (81 > 79 characters)


The user is pointing out two linting errors in `tests/cli/test_app.py` - lines 51 and 63 are too long (over 79 characters). Let me read the file and fix those lines.


    48→def test_mount_fuse_backend_mounts_at_root():
    49→    """--backend memory with --fuse mounts the backend at / prefix."""
    50→    with tempfile.TemporaryDirectory() as mp:
    51→        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
    52→             patch("mirage.cli.commands.workspace._ensure_server"), \
    53→             patch("mirage.cli.commands.workspace._client"):
    54→            mount(backend="memory", mountpoint=mp, **_fuse_defaults())
    55→            ws = mock_fuse.call_args.kwargs["fs"].ws
    56→            prefixes = [m.prefix for m in ws._registry._mounts]
    57→            assert "/" in prefixes
    58→
    59→
    60→def test_mount_fuse_backend_with_extra_spec():
    61→    """--backend combined with positional prefix:uri spec and --fuse."""
    62→    with tempfile.TemporaryDirectory() as mp:
    63→        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
    64→             patch("mirage.cli.commands.workspace._ensure_server"), \
    65→             patch("mirage.cli.commands.workspace._client"):
    66→            mount(["memory:/data/"],
    67→                  backend="memory",

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

def test_mount_fuse_backend_mounts_at_root():
    """--backend memory with --fuse mounts the backend at / prefix."""
    with tempfile.TemporaryDirectory() as mp:
        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
             patch("mirage.cli.commands.workspace._ensure_server"), \
             patch("mirage.cli.commands.workspace._client"):
            mount(backend="memory", mountpoint=mp, **_fuse_defaults())
            ws = mock_fuse.call_args.kwargs["fs"].ws
            prefixes = [m.prefix for m in ws._registry._mounts]
            assert "/" in prefixes

def test_mount_fuse_backend_with_extra_spec():
    """--backend combined with positional prefix:uri spec and --fuse."""
    with tempfile.TemporaryDirectory() as mp:
        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
             patch("mirage.cli.commands.workspace._ensure_server"), \
             patch("mirage.cli.commands.workspace._client"):
            mount(["memory:/data/"],
                  backend="memory",


Edit

        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
             patch("mirage.cli.commands.workspace._ensure_server"), \
             patch("mirage.cli.commands.workspace._client"):
            mount(backend="memory", mountpoint=mp, **_fuse_defaults())
            ws = mock_fuse.call_args.kwargs["fs"].ws
            prefixes = [m.prefix for m in ws._registry._mounts]
            assert "/" in prefixes

def test_mount_fuse_backend_with_extra_spec():
    """--backend combined with positional prefix:uri spec and --fuse."""
    with tempfile.TemporaryDirectory() as mp:
        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
             patch("mirage.cli.commands.workspace._ensure_server"), \
             patch("mirage.cli.commands.workspace._client"):

        _patch = "mirage.cli.commands.workspace"
        with patch(f"{_patch}.fuse_mount_fn") as mock_fuse, \
             patch(f"{_patch}._ensure_server"), \
             patch(f"{_patch}._client"):
            mount(backend="memory", mountpoint=mp, **_fuse_defaults())
            ws = mock_fuse.call_args.kwargs["fs"].ws
            prefixes = [m.prefix for m in ws._registry._mounts]
            assert "/" in prefixes

def test_mount_fuse_backend_with_extra_spec():
    """--backend combined with positional prefix:uri spec and --fuse."""
    with tempfile.TemporaryDirectory() as mp:
        _patch = "mirage.cli.commands.workspace"
        with patch(f"{_patch}.fuse_mount_fn") as mock_fuse, \
             patch(f"{_patch}._ensure_server"), \
             patch(f"{_patch}._client"):


The file /Users/agent/strukto/mirage/tests/cli/test_app.py has been updated successfully.

        _patch = "mirage.cli.commands.workspace"
        with patch(f"{_patch}.fuse_mount_fn") as mock_fuse, \
             patch(f"{_patch}._ensure_server"), \
             patch(f"{_patch}._client"):
            mount(backend="memory", mountpoint=mp, **_fuse_defaults())
            ws = mock_fuse.call_args.kwargs["fs"].ws
            prefixes = [m.prefix for m in ws._registry._mounts]
            assert "/" in prefixes

def test_mount_fuse_backend_with_extra_spec():
    """--backend combined with positional prefix:uri spec and --fuse."""
    with tempfile.TemporaryDirectory() as mp:
        _patch = "mirage.cli.commands.workspace"
        with patch(f"{_patch}.fuse_mount_fn") as mock_fuse, \
             patch(f"{_patch}._ensure_server"), \
             patch(f"{_patch}._client"):

import os
import subprocess
import sys
import tempfile
import time
from unittest.mock import patch

import pytest

from mirage.cli.commands.workspace import mount
from mirage.types import MountMode, RuntimeType
from mirage.workspace.consistency import ConsistencyPolicy

def _run(args: list[str],
         env: dict | None = None,
         check: bool = True) -> subprocess.CompletedProcess:
    e = os.environ.copy()
    if env:
        e.update(env)
    return subprocess.run(
        [sys.executable, "-m", "mirage.cli.app"] + args,
        capture_output=True,
        text=True,
        env=e,
        check=check,
    )

def test_help():
    result = _run(["--help"], check=False)
    assert result.returncode == 0

def _fuse_defaults() -> dict:
    return dict(
        sync_policy="staged",
        mode=MountMode.READ,
        consistency=ConsistencyPolicy.CACHED,
        ttl=300,
        config=None,
        runtime=RuntimeType.MEMORY,
        fuse=True,
        agent_id=None,
    )

def test_mount_fuse_backend_mounts_at_root():
    """--backend memory with --fuse mounts the backend at / prefix."""
    with tempfile.TemporaryDirectory() as mp:
        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
             patch("mirage.cli.commands.workspace._ensure_server"), \
             patch("mirage.cli.commands.workspace._client"):
            mount(backend="memory", mountpoint=mp, **_fuse_defaults())
            ws = mock_fuse.call_args.kwargs["fs"].ws
            prefixes = [m.prefix for m in ws._registry._mounts]
            assert "/" in prefixes

def test_mount_fuse_backend_with_extra_spec():
    """--backend combined with positional prefix:uri spec and --fuse."""
    with tempfile.TemporaryDirectory() as mp:
        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \
             patch("mirage.cli.commands.workspace._ensure_server"), \
             patch("mirage.cli.commands.workspace._client"):
            mount(["memory:/data/"],
                  backend="memory",
                  mountpoint=mp,
                  **_fuse_defaults())
            ws = mock_fuse.call_args.kwargs["fs"].ws
            prefixes = [m.prefix for m in ws._registry._mounts]
            assert "/" in prefixes
            assert any(p.rstrip("/") == "/data" for p in prefixes)

def test_mount_fuse_no_backend_no_spec_raises():
    """mount --fuse with no --backend and no specs exits with error."""
    result = _run(["mount", "--fuse", "--mountpoint", "/tmp/x"], check=False)
    assert result.returncode != 0

@pytest.mark.skip(reason="requires live server")
def test_mount_exec_kill():
    tmpdir = tempfile.mkdtemp(dir="/tmp")
    env = {"MIRAGE_HOME": tmpdir}

    result = _run(["mount", "memory:/data/"], env=env)
    ws_id = result.stdout.strip()
    assert len(ws_id) == 8

    time.sleep(0.3)

    result = _run(["exec", ws_id, "ls", "/data"], env=env)
    assert result.returncode == 0

    result = _run(["workspace", "ls"], env=env)
    assert ws_id in result.stdout

    _run(["kill", ws_id], env=env)
    time.sleep(0.3)

    result = _run(["workspace", "ls"], env=env, check=False)
    assert ws_id not in result.stdout

-        with patch("mirage.cli.commands.workspace.fuse_mount_fn") as mock_fuse, \

-             patch("mirage.cli.commands.workspace._ensure_server"), \

-             patch("mirage.cli.commands.workspace._client"):

+        _patch = "mirage.cli.commands.workspace"

+        with patch(f"{_patch}.fuse_mount_fn") as mock_fuse, \

+             patch(f"{_patch}._ensure_server"), \

+             patch(f"{_patch}._client"):


Bash

uv run pytest tests/cli/test_app.py --no-cov -q 2>&1 | tail -3

Verify tests still pass after lint fix


Uninstalled 1 package in 0.73ms
Installed 1 package in 1ms
....s                                                                    [100%]


uv run pytest tests/cli/test_app.py --no-cov -q 2>&1 | tail -1

Get test summary line


....s                                                                    [100%]


uv run pytest tests/cli/test_app.py --no-cov 2>&1 | tail -1

Get test count


4 passed, 1 skipped in 0.49s


Fixed. Extracted the repeated patch prefix into `_patch` to shorten the lines below 79 chars.