feat: OpenMesh 基础平台与 MD/PDF 转换技能
- 后端: coworker 智能体框架, WS API, 文件上传, 附件处理 - 前端: Open WebUI, 文件全量走 upload API (含 MD/TXT/JSON 等文本类) - 技能: md-to-office (pandoc + wkhtmltopdf) - 修复: 上传文件路径丢失, Agent 搜索浪费, 输出文件跑到 uploads/ - 打包: PyInstaller one-dir, 预打包 pandoc/wkhtmltopdf/chromium
This commit is contained in:
95
coworker/roots.py
Normal file
95
coworker/roots.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""Workspace roots — the directories a session is allowed to touch.
|
||||
|
||||
A Cowork session is "orphan": it owns a per-conversation **scratch** dir (the primary root,
|
||||
writable, the default save location) and may gain access to additional folders, each chosen
|
||||
read-only or read-write. The same `list[RootDir]` object is shared by reference across the
|
||||
PermissionEngine (scoping), the file toolkit (resolution), and the context injector (so the
|
||||
agent is told which dirs it has), so Slice C can mutate it in place at runtime and all three
|
||||
see the change. Index 0 is always the primary.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
|
||||
@dataclass
|
||||
class RootDir:
|
||||
path: Path
|
||||
writable: bool = False
|
||||
label: str = "" # display name; defaults to the dir's basename
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self.path = Path(self.path).expanduser().resolve()
|
||||
if not self.label:
|
||||
self.label = self.path.name or str(self.path)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {"path": str(self.path), "writable": self.writable, "label": self.label}
|
||||
|
||||
|
||||
def normalize_roots(roots: Iterable[Any] | None) -> list[RootDir]:
|
||||
"""Coerce a mixed list (RootDir | dict{path,writable,label} | str/Path) into RootDirs.
|
||||
Bare str/Path entries are treated as read-only; pass dicts/RootDirs to grant write.
|
||||
"""
|
||||
out: list[RootDir] = []
|
||||
for r in roots or []:
|
||||
if isinstance(r, RootDir):
|
||||
out.append(r)
|
||||
elif isinstance(r, dict):
|
||||
out.append(
|
||||
RootDir(
|
||||
path=r["path"],
|
||||
writable=bool(r.get("writable", False)),
|
||||
label=r.get("label", ""),
|
||||
)
|
||||
)
|
||||
elif isinstance(r, (str, Path)):
|
||||
out.append(RootDir(path=r, writable=False))
|
||||
else: # duck-typed object with .path/.writable
|
||||
out.append(
|
||||
RootDir(
|
||||
path=getattr(r, "path"),
|
||||
writable=bool(getattr(r, "writable", False)),
|
||||
)
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def render_context(roots: list[RootDir]) -> str:
|
||||
"""The `<system-context>` body listing the dirs available this turn. Empty when no roots."""
|
||||
if not roots:
|
||||
return ""
|
||||
lines = ["Available directories (you may use file/shell tools within these):"]
|
||||
has_side_scratch = any(i > 0 and r.label == "scratch" for i, r in enumerate(roots))
|
||||
for i, r in enumerate(roots):
|
||||
access = "read-write" if r.writable else "read-only"
|
||||
if i == 0 and r.label == "scratch":
|
||||
tag = " — primary scratch, the default place to save files"
|
||||
elif i == 0:
|
||||
tag = " — the session's workspace (relative paths resolve here)"
|
||||
elif r.label == "scratch":
|
||||
tag = (
|
||||
" — your scratch directory: temporary files, and artifacts you don't "
|
||||
"want to leave inside the workspace"
|
||||
)
|
||||
else:
|
||||
tag = ""
|
||||
lines.append(f"- {r.path} [{access}]{tag}")
|
||||
if has_side_scratch:
|
||||
lines.append(
|
||||
"Relative paths resolve against the workspace; pass an absolute path to use "
|
||||
"another directory. Writes are only allowed in read-write directories. Put "
|
||||
"reports, analyses, and other non-repo deliverables in the scratch directory "
|
||||
"(they appear in the user's Artifacts panel) — write into the workspace only "
|
||||
"for changes that belong in it."
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
"Relative paths resolve against the primary directory; pass an absolute path to use "
|
||||
"another directory. Writes are only allowed in read-write directories. If the user "
|
||||
"cares where a deliverable lands, ask; otherwise save it in the primary scratch."
|
||||
)
|
||||
return "\n".join(lines)
|
||||
Reference in New Issue
Block a user