Files
cowork-local/infrastructure/filesystem/execution_workspace.py
T
f9f6bc01fd
CI / test (push) Canceled after 0s
Feature/delta team/epic r04 (#7)
## Summary

epic r04 - begin refactor

## Change Type

- [x] Cowork feature
- [ ] Bug fix
- [ ] Core AI contribution
- [ ] Test / hardening
- [ ] Performance
- [ ] Documentation

## Related Work

Cowork Task:

Core Repo: http://34.143.229.138/gitea-admin/fsg-ai-core-assets

Core AI Issue:

Core Task:

Related PR:

## Scope

What is intentionally included?

What is intentionally NOT included?

## Validation

- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual verification
- [ ] Regression check

Commands / evidence:

## Security Impact

Permission / credential / network / customer data impact:

## Compatibility

- [ ] No breaking change
- [ ] Breaking change documented

## Reviewer Notes

Anything Cowork reviewers should pay attention to.

---------

Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
Co-authored-by: Huong Le Thi Thien <huongltt35@fpt.com>
Co-authored-by: Nam Pham Dinh Thanh <nampdt@fpt.com>
Co-authored-by: Vu Dam Tuan <vudt15@fpt.com>
Co-authored-by: Hiep Ha Van <hiephv3@fpt.com>
Co-authored-by: Lam Hoang Van <lamhv7@fpt.com>
Reviewed-on: #7
Co-authored-by: Duy Le Huu <duylh19@fpt.com>
2026-08-31 05:15:13 +00:00

94 lines
4.2 KiB
Python

"""ExecutionWorkspace - the output folder vs. the scratch folder for one
turn, as two distinct properties instead of a name convention (R06-T03).
Today the ``.scratch`` subtree is a special case buried inside
``_flatten_rel`` (``infrastructure/filesystem/file_tools.py``): a generator
script writes there, the deliverable lands in the output root, and
``core/chat_agent.py`` cleans ``.scratch`` up after the turn — but nothing
NAMES "the scratch folder" as a thing; every call site re-derives
``workdir / ".scratch"`` (or checks ``Path(rel).parts[0] == ".scratch"``) by
hand. This class gives that convention one home.
It does not change WHERE files land - ``workspace_root/.scratch`` stays
exactly what it always was. It exists so a caller (an application service,
R06-T05's ``FileWorkspaceService``, or a future turn-cleanup step) can ask
for "the output dir" / "the scratch dir" instead of hand-building the path
and hoping the convention hasn't drifted.
SEAM · dựng 2026-08-21 · chưa nối dây (F-05)
------------------------------------------------------------
Được nối khi: một chỗ gọi thật hỏi ``output_dir``/``scratch_dir`` thay vì tự ghép ``workdir / ".scratch"``.
Để dormant thì sao: Quy ước ``.scratch`` vẫn nằm rải trong ``file_tools.py``
và ``core/chat_agent.py``. File này đặt tên cho nó nhưng chưa ai dùng, nên
quy ước vẫn trôi được.
Cổng ``scripts/check_orphan_modules.py`` đếm tuổi seam từ ngày trên
và nhắc khi quá ``SEAM_MAX_AGE_DAYS``. Đổi nội dung dòng đó thì cổng
đọc theo — đừng sửa ngày để làm im lời nhắc.
"""
from __future__ import annotations
import shutil
from dataclasses import dataclass
from pathlib import Path
from cowork_local.domain.workspaces import WorkspaceSession
SCRATCH_DIRNAME = ".scratch"
@dataclass(frozen=True)
class ExecutionWorkspace:
"""The two folders a turn actually writes to, derived from a
:class:`WorkspaceSession`.
``output_dir`` is always the session's ``workspace_root`` itself, not a
per-turn subfolder - Cowork's whole design is that every deliverable lands
directly in the one configured Output folder (see
``infrastructure/filesystem/file_tools.py::_flatten_rel``'s docstring).
``scratch_dir`` is the SAME flat ``workspace_root/.scratch`` every turn on
that workspace already shares today (``core/chat_agent.py``'s
``_cleanup_cowork_intermediates`` operates on that exact path) - this
class does not introduce per-turn namespacing that doesn't exist in the
engine yet, only names the existing convention.
``turn_id`` is kept as metadata for callers that want to attribute a
workspace to the turn that used it (logging, future per-turn scratch
namespacing); it does not affect either path today.
"""
session: WorkspaceSession
turn_id: str
@property
def output_dir(self) -> Path:
"""Thư mục agent được phép ghi kết quả — chính là gốc của phiên làm việc."""
return self.session.workspace_root
@property
def scratch_dir(self) -> Path:
"""Thư mục nháp bên trong phiên, cho file tạm không phải kết quả cuối."""
return self.session.workspace_root / SCRATCH_DIRNAME
def ensure_dirs(self) -> None:
"""Create both folders if they don't exist yet. Callers that only
need one (most do) can skip this and let ``write_file`` create parents
on demand, same as today."""
self.output_dir.mkdir(parents=True, exist_ok=True)
self.scratch_dir.mkdir(parents=True, exist_ok=True)
def cleanup_scratch(self) -> None:
"""Unconditionally remove the scratch subtree.
Coarser than ``core/chat_agent.py::_cleanup_cowork_intermediates``,
which rescues any real deliverable a generator script wrote INSIDE
``.scratch`` before wiping it - that rescue logic stays there. This
is for callers that only need "make the scratch folder go away"
(e.g. before starting a fresh run) and know it holds nothing worth
saving.
"""
shutil.rmtree(self.scratch_dir, ignore_errors=True)
__all__ = ["ExecutionWorkspace", "SCRATCH_DIRNAME"]